<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="https://amrosia.dev/feed_style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <tabi:metadata xmlns:tabi="https://github.com/welpo/tabi">
        <tabi:base_url>https:&#x2F;&#x2F;amrosia.dev</tabi:base_url>
        <tabi:separator>
            •
        </tabi:separator>
        <tabi:about_feeds>This is a web feed, also known as an Atom feed. Subscribe by copying the URL from the address bar into your newsreader. Visit About Feeds to learn more and get started. It&#x27;s free.</tabi:about_feeds>
        <tabi:visit_the_site>Visit website</tabi:visit_the_site>
        <tabi:recent_posts>Recent posts</tabi:recent_posts>
        <tabi:last_updated_on>Updated on $DATE</tabi:last_updated_on>
        <tabi:default_theme>dark</tabi:default_theme>
        <tabi:post_listing_date>date</tabi:post_listing_date>
        <tabi:current_section>audio</tabi:current_section>
    </tabi:metadata><link rel="extra-stylesheet" href="https://amrosia.dev/skins/teal.css?h=bd19e558a52d678a50de" /><title>Segfault diaries - audio</title>
        <subtitle>Welcome to my blog!</subtitle>
    <link href="https://amrosia.dev/tags/audio/atom.xml" rel="self" type="application/atom+xml"/>
    <link href="https://amrosia.dev/tags/audio/" rel="alternate" type="text/html"/>
    <generator uri="https://www.getzola.org/">Zola</generator><updated>2026-09-26T00:00:00+00:00</updated><id>https://amrosia.dev/tags/audio/atom.xml</id><entry xml:lang="en">
        <title>Play audio on two Bluetooth headphones with PipeWire</title>
        <published>2026-09-26T00:00:00+00:00</published>
        <updated>2026-09-26T00:00:00+00:00</updated>
        <author>
            <name>amrosia</name>
        </author>
        <link rel="alternate" href="https://amrosia.dev/blog/play-audio-on-two-bluetooth-headphones-pipewire/" type="text/html"/>
        <id>https://amrosia.dev/blog/play-audio-on-two-bluetooth-headphones-pipewire/</id>
        
            <content type="html">&lt;p&gt;&lt;code&gt;pasystray&lt;&#x2F;code&gt; can choose an output for an application, but it cannot itself duplicate audio to two outputs. On a PipeWire system, create a virtual combined sink that forwards its audio to both Bluetooth headphones, then select that sink in pasystray.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;This is useful for listening with two pairs of headphones from one computer. Both devices must be connected at the same time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;find-the-bluetooth-sink-names&quot;&gt;&lt;a class=&quot;header-anchor no-hover-padding&quot; href=&quot;#find-the-bluetooth-sink-names&quot; aria-label=&quot;Anchor link for: find-the-bluetooth-sink-names&quot;&gt;&lt;span class=&quot;link-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
Find the Bluetooth sink names&lt;&#x2F;h2&gt;
&lt;p&gt;Run:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl list short sinks
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For example, this system has these two Bluetooth sinks:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;88   bluez_output.40:58:99:55:FD:82   PipeWire   float32le 2ch 48000Hz   SUSPENDED
119  bluez_output.7C:E9:13:59:03:80   PipeWire   float32le 2ch 48000Hz   SUSPENDED
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;SUSPENDED&lt;&#x2F;code&gt; is normal when a device is connected but not currently playing audio. The important values are the two &lt;code&gt;bluez_output...&lt;&#x2F;code&gt; names, not the numeric IDs at the beginning of the lines.&lt;&#x2F;p&gt;
&lt;p&gt;To confirm that PipeWire is providing the PulseAudio-compatible server, run:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl info | grep &amp;quot;Server Name&amp;quot;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It should report PipeWire.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;create-the-combined-sink&quot;&gt;&lt;a class=&quot;header-anchor no-hover-padding&quot; href=&quot;#create-the-combined-sink&quot; aria-label=&quot;Anchor link for: create-the-combined-sink&quot;&gt;&lt;span class=&quot;link-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
Create the combined sink&lt;&#x2F;h2&gt;
&lt;p&gt;Use the sink names from the previous command:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl load-module module-combine-sink \
  sink_name=dual_bluetooth \
  sink_properties=device.description=Dual_Bluetooth \
  sinks=bluez_output.40:58:99:55:FD:82,bluez_output.7C:E9:13:59:03:80
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;PipeWire should print a module ID. Check that the new virtual output exists:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl list short sinks
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It should include &lt;code&gt;dual_bluetooth&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In pasystray, choose &lt;strong&gt;Dual_Bluetooth&lt;&#x2F;strong&gt; from the default-sink menu, or route an individual application’s stream to it. To make it the default output from a terminal:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl set-default-sink dual_bluetooth
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;New applications will then send audio to both headphones. Existing applications may need to be moved to &lt;code&gt;dual_bluetooth&lt;&#x2F;code&gt; in pasystray or restarted.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;make-it-persistent&quot;&gt;&lt;a class=&quot;header-anchor no-hover-padding&quot; href=&quot;#make-it-persistent&quot; aria-label=&quot;Anchor link for: make-it-persistent&quot;&gt;&lt;span class=&quot;link-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
Make it persistent&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;code&gt;pactl load-module&lt;&#x2F;code&gt; command only lasts until PipeWire is restarted. To load the virtual sink whenever &lt;code&gt;pipewire-pulse&lt;&#x2F;code&gt; starts, create this user configuration file:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;~&#x2F;.config&#x2F;pipewire&#x2F;pipewire-pulse.conf.d&#x2F;dual-bluetooth.conf
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;with these contents:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;ini&quot;&gt;pulse.cmd = [
    { cmd = &amp;quot;load-module&amp;quot;
      args = &amp;quot;module-combine-sink sink_name=dual_bluetooth sink_properties=device.description=Dual_Bluetooth sinks=bluez_output.40:58:99:55:FD:82,bluez_output.7C:E9:13:59:03:80&amp;quot;
      flags = [ ]
    }
]
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Restart the user PulseAudio-compatibility service after creating the file:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;systemctl --user restart pipewire-pulse
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Connect both headphones, then verify the combined sink again with:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl list short sinks
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The Bluetooth sink names normally contain the headphones’ MAC addresses, so they remain the same across reconnects. If a device is removed and paired again, check its name and update the configuration if its MAC address changed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;remove-the-temporary-sink&quot;&gt;&lt;a class=&quot;header-anchor no-hover-padding&quot; href=&quot;#remove-the-temporary-sink&quot; aria-label=&quot;Anchor link for: remove-the-temporary-sink&quot;&gt;&lt;span class=&quot;link-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
Remove the temporary sink&lt;&#x2F;h2&gt;
&lt;p&gt;For a sink created only with &lt;code&gt;pactl load-module&lt;&#x2F;code&gt;, find its module ID:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl list short modules | grep combine
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then unload it:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;pactl unload-module &amp;lt;module-id&amp;gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For the persistent version, remove or disable the &lt;code&gt;dual-bluetooth.conf&lt;&#x2F;code&gt; drop-in and restart &lt;code&gt;pipewire-pulse&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bluetooth-synchronization-caveat&quot;&gt;&lt;a class=&quot;header-anchor no-hover-padding&quot; href=&quot;#bluetooth-synchronization-caveat&quot; aria-label=&quot;Anchor link for: bluetooth-synchronization-caveat&quot;&gt;&lt;span class=&quot;link-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
Bluetooth synchronization caveat&lt;&#x2F;h2&gt;
&lt;p&gt;This duplicates the audio; it does not make two Bluetooth devices perfectly synchronized. Each headset has its own codec latency, buffer, and clock. The sound can therefore have a small offset or gradually drift between the headphones, which is especially noticeable when both listeners are close together.&lt;&#x2F;p&gt;
&lt;p&gt;PipeWire’s documented &lt;code&gt;module-combine-sink&lt;&#x2F;code&gt; is the appropriate module for this setup. It is generally fine for two people listening separately, but it is not a replacement for purpose-built synchronized multi-headphone hardware.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;references&quot;&gt;&lt;a class=&quot;header-anchor no-hover-padding&quot; href=&quot;#references&quot; aria-label=&quot;Anchor link for: references&quot;&gt;&lt;span class=&quot;link-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
References&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.pipewire.org&#x2F;page_pulse_modules.html&quot;&gt;PipeWire PulseAudio modules: loading modules and persistent drop-ins&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.pipewire.org&#x2F;page_pulse_module_combine_sink.html&quot;&gt;PipeWire &lt;code&gt;module-combine-sink&lt;&#x2F;code&gt; options&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        <summary type="html">pasystray can choose an output for an application, but it cannot itself duplicate audio to two outputs. On a PipeWire system, create a virtual combined sink that forwards its audio to both Bluetooth headphones, then select that sink in pasystray…</summary>
        </entry>
</feed>
