On windows some laptops have the option to enable the playback of different audio streams on different audio outputs, for example you could listen to a video call in the browser through your wired headphones, while playing music from Spotify on the internal speakers.
Such option can have a different name depending on the sound card or laptop's vendor, for my laptop, an HP Omen 15-dc100xxx with a ALC295 card it is called multistreaming and can be enabled from the OMEN Audio Control program.
Here's some screenshot showcasing that feature on Windows:
And this is how you route programs' audio to an individual output:
On Linux I don't have such an option, so can only listen one output at a time
That is:
- You have to unplug your wired speakers/headphones to be able to play anything from the internal speakers,
- And if you connect a wired speakers/headphones:
If you follow until part 2
- Allow selecting and playing audio from the internal speakers while a wired output is connected
If you follow till the end
- Have a separate audio sink for each output
- Play different audio streams on each card port, simultaneously (e.g. Spotify on speakers and Firefox on Headphones)
Note: The program used in above screenshots is pavucontrol
- PipeWire
- alsa-card-profiles
- alsa-utils
- alsa-tools
- pavucontrol
Run pactl list cards
and save the output somewhere, bellow is a stripped
version of mine to keep the important things
Card #46
Name: alsa_card.pci-0000_00_1f.3
Driver: alsa
Properties:
api.alsa.path = "hw:0"
device.product.id = "0xa348"
device.vendor.id = "0x8086"
...
Profiles:
output:analog-stereo+input:analog-stereo: Analog Stereo Duplex (sinks: 1, sources: 1, priority: 6565, available: yes)
output:analog-stereo: Analog Stereo Output (sinks: 1, sources: 0, priority: 6500, available: yes)
input:analog-stereo: Analog Stereo Input (sinks: 0, sources: 1, priority: 65, available: yes)
pro-audio: Pro Audio (sinks: 4, sources: 1, priority: 1, available: yes)
Active Profile: output:analog-stereo+input:analog-stereo
Ports:
analog-output-speaker: Speakers (type: Speaker, priority: 10000, latency offset: 0 usec, availability group: Legacy 3, availability unknown)
Properties:
port.type = "speaker"
port.availability-group = "Legacy 3"
device.icon_name = "audio-speakers"
card.profile.port = "2"
Part of profile(s): output:analog-stereo, output:analog-stereo+input:analog-stereo
analog-output-headphones: Headphones (type: Headphones, priority: 9900, latency offset: 0 usec, availability group: Legacy 4, availability unknown)
Properties:
port.type = "headphones"
port.availability-group = "Legacy 4"
device.icon_name = "audio-headphones"
card.profile.port = "3"
Part of profile(s): output:analog-stereo, output:analog-stereo+input:analog-stereo
Card details:
Name: alsa_card.pci-0000_00_1f.3
Ports: analog-output-speaker analog-output-headphones
Profiles: output:analog-stereo input:analog-stereo output:analog-stereo+input:analog-stereo
device.product.id = "0xa348"
device.vendor.id = "0x8086"
As we can see, this laptop has three audio profiles, one for all audio outputs and other for all inputs, with an extra one that has both inputs and outputs (yours may vary)
Run pactl list sinks
and save the output, bellow is a stripped
version of mine to keep the important things
Sink #47
State: RUNNING
Name: alsa_output.pci-0000_00_1f.3.analog-stereo
Description: Built-in Audio Analog Stereo
Driver: PipeWire
...
Ports:
analog-output-speaker: Speakers (type: Speaker, priority: 10000, availability group: Legacy 3, not available)
analog-output-headphones: Headphones (type: Headphones, priority: 9900, availability group: Legacy 4, available)
Active Port: analog-output-headphones
Formats:
pcm
Sink details:
Name: alsa_output.pci-0000_00_1f.3.analog-stereo
Ports: analog-output-speaker analog-output-headphones
Active Port: analog-output-headphones
As we can see, there is only one audio sink that can play audio on the two outputs individually (yours may vary)
Note: alsa-card-profile
files are provided by alsa-card-profiles
package
-
First copy the contents of folder
/usr/share/alsa-card-profile/mixer/paths/
to/etc/alsa-card-profile/mixer/paths/
mkdir -p /etc/alsa-card-profile/mixer/ sudo cp -r /usr/share/alsa-card-profile/mixer/paths/ /etc/alsa-card-profile/mixer/
-
Next we need to modify the mixer path in
/etc/alsa-card-profile/mixer/paths/
that matches the speakers port, in my case isanalog-output-speaker
.It is crucial to pick the correct one, to verify you did, change
description-key
value to something else (e.g. by removing the last letter) and restart pipewire (systemctl restart --user pipewire pipewire-pulse pipewire.socket wireplumber
) the mixer path file name will show in pavucontrol orpactl list sinks
instead of the actual port name:Ports: analog-output-speaker: analog-output-speaker (type: Unknown, priority: 10000, availability group: Legacy 4, available)
-
Delete all the other files in
/etc/alsa-card-profile/mixer/paths/
leaving only your mixer path and the common file e.g:sudo find /etc/alsa-card-profile/mixer/paths/ -type f ! -name 'analog-output-speaker.conf' ! -name 'analog-output.conf.common' -exec rm -f {} +
-
Change the
description-key
back to the default, then:-
Set
state.plugged = unknown
inside the Jack section that best matches the wired port name of your card, in my case isanalog-output-headphones
so I use[Jack Headphone]
one:[Jack Headphone] state.plugged = unknown state.unplugged = unknown
-
Comment (by adding semi-colon on start of line) the Element section that matches the port
[Jack ...]
in my case is[Element Headphone]
:; [Element Headphone] ; switch = off ; volume = off
-
-
Save the changes and restart the audio server by running:
systemctl restart --user pipewire pipewire-pulse pipewire.socket wireplumber
If everything went well you should have the speaker option available without having to unplug your wired device and
- Should be able to play audio on them (individually)
- Audio streams should still change automatically from speakers to your wired output and vice versa when plugging/unplugging a wired device
Congratulations! We're closer to our final goal, but you may stop here if this was your desired behavior 🙂
3 Splitting for simultaneous playback with alsa firmware patch (for different applications on each port)
First we need to identify and check if our card has more than one sub device and test if sound comes out of them
Run
aplay -l
You'll get something like the following
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC295 Analog [ALC295 Analog]
Subdevices: 1/1
Subdevice #0: subdevice #0
...
At this point, my card card 0
(ALC295), has only one sub device, you may have more, take note of this information as we'll need it later
We can confirm the playback (output) and capture (input) streams the card currently has with cat /proc/asound/pcm
00-00: ALC295 Analog : ALC295 Analog : playback 1 : capture 1
...
- Run
hdajackretask
- In the Select a codec drop-down select your card
- In the Options section check
Parser hints
- In the Hints list set
indep_hp
and `` to yes with double click on them. - Press
Install boot override
- Open the file
/lib/firmware/hda-jack-retask.fw
and addvmaster=no
belowindep_hp=yes
- Reboot to apply the changes
Run
cat /proc/asound/card*/codec#* | grep -E 'Codec|Vendor Id|Subsystem Id|Address'
You'll get the codecs in the following format, pick the one that matches your card (mine is the Realtek ALC295
):
Codec: Realtek ALC295
Address: 0
Vendor Id: 0x10ec0295
Subsystem Id: 0x103c8575
...
With the above we can start creating our patch file:
-
Create the file (don't copy as is, modify according to explanation bellow):
/lib/firmware/hda-jack-retask.fw
[codec] 0x10ec0295 0x103c8575 0 [hints] indep_hp=yes vmaster=no
Below
[codec]
line we should put the values ofVendor Id
, theSubsystem Id
andAddress
of the card, separated by spaces.Below
[hints]
we need to add what is called hint strings-
indep_hp=yes
this will make for our jack output to be detected as an independent PCM stream with its own controls (meaning it will split away from internal speakers as a separate audio sink for us to play specific app stream on it). -
vmaster=no
will disable the virtual Master control so we can control volume on each port individually.
-
-
Create the following file:
/etc/modprobe.d/alsa-base.conf
options snd-hda-intel patch=hda-jack-retask.fw
-
Reboot to apply the changes
For immutable distros /lib/firmware/
is not writable. As a workaround you can use an udev rule that sets the hints on boot. This method may cause some noises during boot and is not warranted to be as reliable as the firmware one, if that's the case, suggestions to improve it are welcome
-
Create the file
/etc/udev/rules.d/91-pipewire-alsa-port-split.rules
(don't copy as is, modify according to explanation bellow)SUBSYSTEM!="sound", GOTO="pipewire_end" ACTION!="change", GOTO="pipewire_end" KERNEL!="card*", GOTO="pipewire_end" SUBSYSTEMS=="pci", ATTRS{vendor}=="0x8086", ATTRS{device}=="0xa348", \ RUN+="/usr/local/bin/alsa-split-ports.sh" LABEL="pipewire_end"
-
Create the script
/usr/local/bin/alsa-split-ports-hints.sh
and setCODEC
VENDOR_ID
andSUBSYSTEN_ID
with the ones from your card fromcat /proc/asound/card*/codec#* | grep -E 'Codec|Vendor Id|Subsystem Id|Address'
, note how theCODEC
variable doesn't have the vendor name (Realtek
) because whe are matching against/sys/class/sound/hw*/chip_name
#!/usr/bin/env bash CODEC="ALC295" VENDOR_ID="0x10ec0295" SUBSYSTEN_ID="0x103c8575" HINTS="indep_hp = yes vmaster = no " get_codec_hwdep() { local codec=$1 local vendor_id=$2 local subsystem_id=$3 local addr="" [[ -z "$codec" || -z "$vendor_id" || -z "$subsystem_id" ]] && { echo "ERROR: Not enough arguments given"; return; } for file in /sys/class/sound/hw*; do if [[ -n "$addr" ]]; then echo "$addr" return fi if grep -q "$codec" "$file/chip_name" && grep -q "$vendor_id" "$file/vendor_id" && grep -q "$subsystem_id" "$file/subsystem_id"; then addr=$file fi done if [[ -z "$addr" ]]; then echo "ERROR: Could not get address for c:$codec v:$vendor_id s:$subsystem_id" return fi } # get_codec_hwdep "$CODEC" "$VENDOR_ID" "$SUBSYSTEN_ID" hwdep="$(get_codec_hwdep "$CODEC" "$VENDOR_ID" "$SUBSYSTEN_ID")" if [[ "$hwdep" == *"ERROR"* || -z "$hwdep" ]]; then exit 1 fi while IFS=$'\n' read -r line; do if [[ -z "$line" ]]; then continue fi echo "$line > ${hwdep}/hints" echo "$line" > "${hwdep}"/hints done <<< "$HINTS" echo "echo 1 > ${hwdep}/reconfig" echo 1 > "${hwdep}"/reconfig # wait some time to intialize before restoring sleep 5 alsactl restore
-
Reboot to apply the changes
Run again
aplay -l
You should get something like the following
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC295 Analog [ALC295 Analog]
Subdevices: 0/1
Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 2: ALC295 Alt Analog [ALC295 Alt Analog]
Subdevices: 1/1
Subdevice #0: subdevice #0
Now the card ALC295 (card 0
), has an extra sub device (2
), interesting...
Run cat /proc/asound/pcm
, if there is a new sub device and has a playback sub-stream (playback 1
) like below you can continue
00-00: ALC295 Analog : ALC295 Analog : playback 1 : capture 1
00-02: ALC295 Alt Analog : ALC295 Alt Analog : playback 1
...
-
Plug-in your wired audio device then run
alsamixer -c0
(replace 0 with your card #number if needed)- Un-mute any muted device (the ones with MM below the volume slider) pressing
m
- Increase their volume to around 30 if the're on 0
- Enable
Independent HP
if is not enabled - Press
Esc
to exit.
- Un-mute any muted device (the ones with MM below the volume slider) pressing
-
Finally, save it by running
sudo alsactl store
-
Stop any running pipewire services (may need to stop it multiple times if it gets restarted):
systemctl --user stop pipewire.service pipewire.socket pipewire-pulse.service pipewire-pulse.socket wireplumber.service
-
Run
speaker-test -Dhw:0,0 -c2
(replace 0,0 with the card #number and device #numbers from your card fromaplay -l
)For the device 0 (speaker-test -Dhw:0,0 -c2) the sound comes out from the speakers, so
device 0
is the speakers -
Repeat for all the devices for your card and note which device corresponds to which physical output
So my card's outputs are the following (yours may vary):
- Device
0,0
(card 0, device 0) handles Speakers (and also microphones, as we saw incat /proc/asound/pcm
) - Device
0,2
(card 0, device 2) handles Headphones
Card details:
Name: alsa_card.pci-0000_00_1f.3
Ports: analog-output-speaker analog-output-headphones
Profiles: output:analog-stereo input:analog-stereo output:analog-stereo+input:analog-stereo
Sink details:
Name: alsa_output.pci-0000_00_1f.3.analog-stereo
Ports: analog-output-speaker analog-output-headphones
Active Port: analog-output-headphones
Card sub devices and playback (outputs) / capture (inputs) location:
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC295 Analog [ALC295 Analog]
Subdevices: 0/1
Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 2: ALC295 Alt Analog [ALC295 Alt Analog]
Subdevices: 1/1
Subdevice #0: subdevice #0
00-00: ALC295 Analog : ALC295 Analog : playback 1 : capture 1
00-02: ALC295 Alt Analog : ALC295 Alt Analog : playback 1
...
So in my case I have:
- Two ports:
analog-output-speaker analog-output-headphones
- Device locations for the card 0 correspond to:
- Device
0,0
(card 0, device 0) Speakers (also handles microphones) - Device
0,2
(card 0, device 2) Headphones
- Device
-
Rename your mixer path file from 2 disable Headphone jack detection for speakers like below:
sudo mv /etc/alsa-card-profile/mixer/paths/analog-output-speaker.conf /etc/alsa-card-profile/mixer/paths/analog-output-speaker-split.conf
-
Now create the file
/etc/alsa-card-profile/mixer/profile-sets/split-ports-profile.conf
sudo mkdir /etc/alsa-card-profile/mixer/profile-sets/ sudo touch /etc/alsa-card-profile/mixer/profile-sets/split-ports-profile.conf
-
Open split-ports-profile.conf with your preferred editor, paste the following and adapt it to your system according to the comments:
; This will let alsa generate automatic profiles (e.g internal speaker + microphone) [General] auto-profiles = yes ; device-strings describes the ALSA device string(s) that PulseAudio uses to open the device, where "%f" specifies the card number (should always be present in the string). ; This is the mapping for the internal speaker ; If needed, change the 0 in "hw:%f,0" to your sub device location ; You can change the description for this and other mappings if you want ; in paths output put the name of the previously created custom mixer path [Mapping analog-stereo-speaker] description = Speakers device-strings = hw:%f,0 paths-output = analog-output-speaker-split channel-map = left,right direction = output ; This is the mapping for the jack output (headphones) ; If needed, change the 2 in "hw:%f,2" to your sub device location ; in paths output put the name of the from card details [Mapping analog-stereo-headphones] description = Headphones device-strings = hw:%f,2 paths-output = analog-output-headphones channel-map = left,right direction = output ; This is the mapping that will handle internal and external microphones, as you could see in `cat /proc/asound/pcm`, the card also had a capture port ; in the 0,0 sub device location so let's add it here too (change the 0 in "hw:%f,0" to your sub device location that has the capture port) ; All the paths-input names here came from the default.conf profile set and you may have to adapt it if your input port name is not included [Mapping analog-stereo-input] description = Microphone device-strings = hw:%f,0 channel-map = left,right paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic direction = input ; Broken in parts the profile name means to join ; The name of the Mapping containing the analog-output-headphones (output:analog-stereo-headphones) ; The name of the Mapping containing the analog-output-speaker (output:analog-stereo-speaker) ; The name of the Mapping containing the analog-stereo-input (input:analog-stereo-input) ; in output-mappings put the name of the output mappings ; input-mappings put the name of the input mappings ; NOTE: Not to be confused width the paths-output/paths-input inside the mapping, we're not using those directly ; This is the profile that will have the internal speakers + jack output + all microphones ; in paths output put the name of the from card details [Profile output:analog-stereo-headphones+output:analog-stereo-speaker+input:analog-stereo-input] description = Analog Stereo Duplex output-mappings = analog-stereo-headphones analog-stereo-speaker input-mappings = analog-stereo-input priority = 80 ; This profile will have the internal speakers + jack output, but not microphones [Profile output:analog-stereo-headphones+output:analog-stereo-speaker] description = Analog Stereo Outputs Only output-mappings = analog-stereo-headphones analog-stereo-speaker priority = 70
-
Create the file
/etc/udev/rules.d/91-pipewire-alsa-port-split.rules
(don't copy as is, modify according to explanation bellow). Also, if using the udev rule to apply hints from Using script and udev rule for immutable distros you can use the commented rule instead.SUBSYSTEM!="sound", GOTO="pipewire_end" ACTION!="change", GOTO="pipewire_end" KERNEL!="card*", GOTO="pipewire_end" SUBSYSTEMS=="pci", ATTRS{vendor}=="0x8086", ATTRS{device}=="0xa348", \ ENV{ACP_PROFILE_SET}="/etc/alsa-card-profile/mixer/profile-sets/split-ports-profile.conf" # Use this instead for immutable distributions # SUBSYSTEMS=="pci", ATTRS{vendor}=="0x8086", ATTRS{device}=="0xa348", \ # ENV{ACP_PROFILE_SET}="/etc/alsa-card-profile/mixer/profile-sets/split-ports-profile.conf" \ # RUN+="/usr/local/bin/alsa-split-ports.sh" LABEL="pipewire_end"
-
Replace the vendor and device id with
device.vendor.id
anddevice.product.id
respectively, that you got in 1.1 Gather some information about the card -
Reboot to apply the changes
Open pavucontrol and in the Configuration tab select the Analog Stereo Duplex
profile for your card
Run
pactl list sinks | grep -E 'Name|Desc|State|Port|device.profile-set'
I everything went well you should have a separate audio sink for each output:
State: IDLE
Name: alsa_output.pci-0000_00_1f.3.analog-stereo-headphones
Description: Built-in Audio Headphones
device.profile-set = "/etc/alsa-card-profile/mixer/profile-sets/split-ports-profile.conf"
Ports:
Active Port: analog-output-headphones
State: SUSPENDED
Name: alsa_output.pci-0000_00_1f.3.analog-stereo-speaker.2
Description: Built-in Audio Speakers
device.profile-set = "/etc/alsa-card-profile/mixer/profile-sets/split-ports-profile.conf"
Ports:
Active Port: analog-output-speaker-split
Now you should be able to play different applications on each sink, you can do that with pavucontrol or KDE's Audio Volume widget. Congratulations! 🎉
If you faced any problems or are stuck please open a new issue including the information as described here
This guide is the result of days of effort hunting for the right information and lots of reading. If it was useful to you consider making a small donation