82

I need to switch the pulseaudio server of a currently playing audio stream.

Pulseaudio server is set up with IP 192.168.1.105

$>cat /etc/pulse/default.pa
...
load-module module-esound-protocol-tcp auth-anonymous=1
load-module module-native-protocol-tcp auth-anonymous=1
load-module module-zeroconf-publish
...

On the source side VLC media player is playing a song.

I created a new tunnel sink with pulseaudio on source side.

pacmd load-module module-tunnel-sink server=192.168.1.105
pacmd set-default-sink 1

But while playing audio the server couldn't be changed immediately. Only after stopping the player and replay, it's ok.

From "gnome-volume-control" or "gnome-control-center sound" switching of output devices is applied immediately.

How can I apply switching the output sink immediately from command line during playback of a sound file?

0

14 Answers 14

121

PulseAudio pacmd is not capable of switching the default sinks while there is an actively playing stream to the sink input. However there is a way to still achieve this.

Changing default sink from command line

First we need to determine the index number of the sinks we want to switch. This can be done by calling:

pacmd list-sinks

Depending on our system this will give you a more or less lengthy list of sinks and properties that are at present accessible:

 >>> 2 sink(s) available.
      * index: 0
            name: <alsa_output.pci-0000_01_00.1.hdmi-stereo-extra1>
            driver: <module-alsa-card.c>
    :
    :
        index: 1
            name: <alsa_output.pci-0000_00_14.2.analog-stereo>
            driver: <module-alsa-card.c>

The index or the name given here is what we need for adressing the sink by command line. The present default sink is marked with an asterix (here 0).

To be able to switch the default sinks from command line we may need to disable stream target device restore by editing the corresponing line in /etc/pulse/default.pa to:

pacmd load-module module-stream-restore restore_device=false

To change the default output sink to sink 1 we then run

pacmd set-default-sink 1

Sucess can be visualized by opening the Sound Settings menu.

Moving a stream to another sink

Changing the default sink while we have an active input stream playing to a given sink has no effect. This should rather be done by moving this input to another sink.

pacmd list-sink-inputs

will tell us the index of the input stream

>>> 1 sink input(s) available.
    index: 5
    driver: <protocol-native.c>

We now know that we want to move the input stream 5 to sink 1 by calling

pacmd move-sink-input 5 1

or back to sink 0 if we like. This will be done immediately without the need of stopping playback.

Changing default sink while playing

Of course we can combine those two commands to immediately switch default sinks during playback e.g. with

pacmd set-default-sink 1 & pacmd move-sink-input 5 1

A drawback of this method is that the input stream index changes every time we stop and restart the music player. So we always have to find out the current stream index before we can switch using the commmand line.

7
  • 2
    You can reference a sink by its index OR by its name pactl list short sinks > 4 bluez_sink.FC_A8_9A_2C_EB_0B module-bluez5-device.c s16le 1ch 8000Hz IDLE now just reference the name pactl set-default-sink bluez_sink.FC_A8_9A_2C_EB_0B
    – Leif Liddy
    Jan 21, 2017 at 21:57
  • 1
    @LeifLiddy: Thank you for the note. In more recent versions of Pulseaudio you can indeed reference to the name of a sink on set-default-sink but you will still need the index number on move-sink-input.
    – Takkat
    Jan 22, 2017 at 7:27
  • And what if 0 sink input(s) available.? But that shitty flash player is still playing audio? Sep 29, 2017 at 1:03
  • And I see flash player in the client(s) logged in section of pacmd list Sep 29, 2017 at 1:06
  • @Zhigalin Flash is long dead on my systems so I can't reall say... it may play directly through ALSA.
    – Takkat
    Sep 29, 2017 at 6:03
36

I have written a simple script to move all the sink-inputs automatically using pacmd.

Usage: ./move-sink-inputs.sh <sink number>

#!/usr/bin/env bash

echo "Setting default sink to: $1";
pacmd set-default-sink "$1"
pacmd list-sink-inputs | grep index | while read -r line; do
  echo "Moving input: ";
  echo "$line" | cut -f2 -d' ';
  echo "to sink: $1";
  pacmd move-sink-input "$(echo "$line" | cut -f2 -d' ')" "$1"
done
3
  • 1
    Awesome! I extended this a little further for my own use: I wanted an icon on my taskbar that would toggle between two audio cards. I wrote a tiny script in Ruby to determine the current sink and call out to this shell script to make the change (pastebin.com/xb3i2ejW). After that, I put both scripts in ~/bin and customized a KDE icon to run the ruby script. This could be done more elegantly, or entirely in shell script, but it works for me.
    – Pathogen
    Jul 2, 2015 at 0:10
  • 4
    Thanks for this. Here's a version that switches to the last (? - I only have two) unused output: pastebin.com/raw/sidH7QPb i.e. it toggles the output, suitable for mapping to a hot key.
    – Julian
    Mar 3, 2016 at 18:35
  • THANKS, really useful script.
    – jameh
    May 23, 2020 at 20:31
9

Improved version of @Gaco script

#!/usr/bin/env bash

case "${1:-}" in
  (""|list)
    pacmd list-sinks |
      grep -E 'index:|name:'
    ;;
  ([0-9]*)
    echo switching default
    pacmd set-default-sink $1 ||
      echo failed
    echo switching applications
    pacmd list-sink-inputs |
      awk '/index:/{print $2}' |
      xargs -r -I{} pacmd move-sink-input {} $1 ||
        echo failed
    ;;
  (*)
    echo "Usage: $0 [|list|<sink name to switch to>]"
    ;;
esac

my runtime copy is on github and it includes also automated switching of Master channel for kmix

7

Based on Gaco's answer, I rewrote it a bit for my personal use. Maybe someone finds it useful. It's for toggling my USB Speakers and USB Gaming headset.

#!/bin/bash

# get list of sinks/cards (for settings CARD1/CARD2)
# pacmd list-sinks | awk '/name:/ {print $0};' | awk '{ print $2}' | sed 's/<//g; s/>//g'

CARD1="alsa_output.usb-C-Media_INC._C-Media_USB_Audio-00"
CARD2="alsa_output.usb-Kingston_HyperX_Virtual_Surround_Sound_00000000-00"

CURRENT_SINK=$(pacmd stat | awk -F": " '/^Default sink name: /{print $2}' | awk 'BEGIN{FS=OFS="."} NF--' | sed 's/alsa_output/alsa_output/g')


function setCard() {

  if [ "$CURRENT_SINK" == "$1" ]
   then
     echo "Already using this Sink"
     exit 1
  fi

  NEW_SINK=$(pacmd list-sinks | awk '/index:/ {print $1 $2 $3} /name:/ {print $0};' | grep -m1 -B1 "$1" | grep index | awk '{print $1}' | cut -d ":" -f2)
  SINK=$(pacmd set-default-sink "$NEW_SINK")
  INPUT=$(pacmd list-sink-inputs | grep index | awk '{print $2}')

  pacmd move-sink-input "$INPUT" "$NEW_SINK"
  echo "Moving input: $INPUT to sink: $NEW_SINK";
  echo "Setting default sink to: $NEW_SINK";

  notify-send --urgency=low "Audio Switching" "SINK: $NEW_SINK"
}

function toggleSinks() {
  if [ "$CURRENT_SINK" == "$CARD1" ]
    then
      setCard $CARD2
    else
      setCard $CARD1
    fi
}


function showHelp() {
  echo "------------------------------------"
  echo "AUDIO SINK SWITCHER"
  echo " "
  echo "$0 [options]"
  echo " "
  echo "options:"
  echo "-h  --help        What you are looking at.."
  echo "-g, --gaming      Sets Gaming headset as output device"
  echo "-s, --speakers    Sets Speakers as output device"
  echo "-t, --toggle      Toggles the different output devices"
  echo " "
  echo "------------------------------------"
}

# check args length
if [ $# -eq 0 ]
  then
    echo "Toggling output devices (Speakers/Headset)"
    toggleSinks
fi


# arg options
while test $# -gt 0; do
  case "$1" in
    -h|--help)
        showHelp
        exit 1
        ;;
    -g|--gaming)
        setCard $CARD2
        exit 1
        ;;
    -s|--speakers)
        setCard $CARD1
        exit 1
        ;;
    -t|--toggle)
        toggleSinks
        echo "Toggling output devices (Speakers/Headset)"
        exit 1
        ;;
     *)
        showHelp
        exit 1
        ;;
  esac
done
2
  • Thank You that is exactly what I needed ! I use now two "soundcards" one is external USB DAC/AMP which I use to listening to music and one is built-in soundcard (realtek) which I use to talk via voice apps. Now I can easily toggle between them. Mar 1, 2020 at 3:05
  • Great script, really appreciate it! Here I was getting ready to spend my day writing a script and figuring out how sound cards work in the process Mar 15, 2020 at 14:37
5

CIRCLE-TOGGLE SINKS.

The Gaco script with just one more line to circle-toggle through available sinks.

#!/bin/bash 

new_sink=$(pacmd list-sinks | grep index | tee /dev/stdout | grep -m1 -A1 "* index" | tail -1 | cut -c12-)

echo "Setting default sink to: $new_sink";
pacmd set-default-sink $new_sink
pacmd list-sink-inputs | grep index | while read line
do
echo "Moving input: ";
echo $line | cut -f2 -d' ';
echo "to sink: $new_sink";
pacmd move-sink-input `echo $line | cut -f2 -d' '` $new_sink

done
1
  • perfect! just what I was looking for, thanks! Now I can put it on a keyboard shortcut. Mar 2, 2022 at 0:46
3

I cobbled together stuff from a few different places and came up with this script to set up a bluetooth JAMBOX once it has been paired. Your MAC will be different of course. Since I control the JAMBOX volume via the Clementine app, 130% volume works best in my case. You might want to change this, along with the rhythmbox.png (it was the only speaker picture I could find on my computer). The error checking is rudimentary, but works pretty reliably. I put this in a desktop launcher for convenience on an ASUS netbook.

#!/bin/bash
# setjambox connection setup
# Find the particulars of your environment with 'pactl list sinks'
# This script uses the sink name instead of the index number
# You also need libnotify-bin to run this script

# Enter the bluetooth MAC address of your device here
MAC=00:21:3C:9F:19:AD

# Make ready
# Convert device address per pulseaudio standards
DEV=$(echo $MAC|tr ':' '_')
TITLE="JAMBOX $MAC"
CONNECTED="Audio connection updated."
PROBLEM="Unable to update settings."
JBLOGO=/usr/share/icons/hicolor/48x48/apps/rhythmbox.png

# And go

pactl list short sink-inputs | while read stream; do
    streamId=$(echo $stream | cut '-d ' -f1)
    pactl move-sink-input "$streamId" bluez_sink.$DEV
done

pactl set-default-sink bluez_sink.$DEV
pactl set-card-profile bluez_card.$DEV a2dp
pactl set-sink-volume  bluez_sink.$DEV 130%

if [ $? -eq 0 ]
then
   notify-send -i $JBLOGO -t 3000 "$TITLE" "$CONNECTED" 
else
   notify-send -i gtk-dialog-warning -t 3000 "$TITLE" "$PROBLEM"
fi
1
  • 1
    +1 for using pactl list short. You can simplify the following line by doing the cut directly after: pactl list short sink-inputs | cut -f1 | while read streamId works for me.
    – bodo
    Apr 18, 2018 at 11:44
3

It appears to be much simpler now on Ubuntu 22.04. No need to change PulseAudio's configuration or to move active streams.

Here is just what is necessary for me to switch from the speakers and microphone of my computer, to the ones of my headset:

#!/bin/bash

if [ "$(pactl get-default-sink)" = "alsa_output...<first-device-speaker>" ]; then
    pactl set-default-sink "alsa_output...<second-device-speaker>"
    pactl set-default-source "alsa_input...<second-device-microphone>"
else
    pactl set-default-sink "alsa_output...<first-device-speaker>"
    pactl set-default-source "alsa_input...<first-device-microphone>"
fi

I assigned a keyboard shortcut to this script, and I can switch from one device to another easily. Active streams switch instantly.

To get the list of available sinks and sources:

  • pactl list short sinks
  • pactl list short sources
2
  • This answer was pure gold to me, also considering this related unix.SE Q/A. To make the output of pactl list more beautiful, you can use ... | column -ts $'\t'. Hope this helps at another occasion too.
    – Cadoiz
    Nov 8, 2022 at 7:42
  • notice: If your goal is to only switch between audio output devices, ignore the microphones / input devices (set-default-source) in the script.
    – user598527
    May 17, 2023 at 11:09
2

There is a ruby script (that I originally forked and rewrote sections of) that lets you change default sinks, volume, and mute status from the command line.

1

And here's a script that will toggle between sinks as well:

http://marginalhacks.com/index.0.html#pulse-switch-out

Here's the script below:

#!/usr/bin/ruby
# Filename: pulse-switch-out
# Author:   David Ljung Madison <DaveSource.com>
# See License:  http://MarginalHacks.com/License/
# Description:  Switch pulse audio output (sink) using pacmd

PACMD = %w(pacmd)

##################################################
# Usage
##################################################
def fatal(*msg)
    msg.each { |m| $stderr.puts "[#{$0.sub(/.*\//,'')}] ERROR: #{m}" }
    exit(-1);
end

def usage(*msg)
    msg.each { |m| $stderr.puts "ERROR: #{m}" }
    $stderr.puts <<-USAGE

Usage:  #{$0.sub(/.*\//,'')} [sink]
  Switch sound playback device for ALSA/pulseaudio

    [sink]   Specify sink number to use (see 'pacmd list-sinks')

    USAGE
    exit -1;
end

def parseArgs
    opt = Hash.new
    loop {
        if (arg=ARGV.shift)==nil then break
        elsif arg == '-h' then usage
        elsif arg == '-?' then usage
        #elsif arg == '-arg' then opt[:arg] = true
        elsif arg =~ /^(\d)$/ then opt[:sink] = arg.to_i
        else
            usage("Unknown arg [#{arg}]")
        end
    }

    opt
end

# Unfortunately you can't return or break from the yield without leaving
# the pipe open, maybe use some sort of ensure and figure out how to close?
def pipe(cmd)
        # This is leaving files open
    #IO.popen(cmd.join(' ')).each { |l|
    a = `#{cmd.join(' ')}`
    ret = $?
    a.split("\n").each { |l|
        yield l
    }
    $?
end

def getSinks(ins=false)
    cmd = PACMD.dup
    cmd.push(ins ? 'list-sink-inputs' : 'list-sinks')
    curr = nil
    sinks = Array.new
    pipe(cmd) { |l|
        next unless l=~/\s*(\*)?\s*index:\s+(\d+)/
        i = $2.to_i
        sinks.push(i)
        curr = i if $1
    }
    return sinks,curr
end

##################################################
# Main code
##################################################
def main
    opt = parseArgs

    sinks,curr = getSinks

    usage("No sinks found?") if sinks.empty?
    usage("Only one sink found") if sinks.size==1

    if opt[:sink]
        usage("Unknown sink [#{opt[:sink]}] (out of #{sinks.join(' ')})") unless sinks.index(opt[:sink])
    else
        # Find next sink after curr
        opt[:sink] = sinks[0]
        sinks.each { |s|
            next unless s>curr
            opt[:sink] = s
            break
        }
    end

    # Set default sink
## For some reason this doesn't change the behavior of new apps.
    puts "Set sink: #{opt[:sink]}"
    system("#{PACMD} set-default-sink #{opt[:sink]} > /dev/null")
    usage("Couldn't set default sink [#{opt[:sink]}]") unless $?==0

    # And move all sink-inputs to the new sink
    ins,ignore = getSinks(true)
    ins.each { |i|
        puts "Move playback #{i} to sink #{opt[:sink]}"
        system("#{PACMD} move-sink-input #{i} #{opt[:sink]} > /dev/null")
        usage("Couldn't move playback #{i} to sink [#{opt[:sink]}]") unless $?==0
    }
end
main
1
  • Done, though unfortunately this means the script doesn't get updated here unless I remember that it's here. As an example, ruby changed the way it handles .each for strings (in the backtick portion) so I needed to update line 53 above. Or just download the script to make sure it's up to date. Mar 2, 2016 at 0:11
1

I adapted @mpapis' version of Gaco's answer to a simple "toggle sink0 or sink1" when run:

#!/bin/bash
SINK_INDEX1=0
SINK_INDEX2=1
ACTIVE_SINK=$(pacmd list-sinks | grep '* index:' | grep -o '[0-9]*')
if [ "$ACTIVE_SINK" = $SINK_INDEX1 ] ; then
    pacmd set-default-sink $SINK_INDEX2
    pacmd list-sink-inputs | awk '/index:/{print $2}' | xargs -r -I{} pacmd move-sink-input {} $SINK_INDEX2
else 
    pacmd set-default-sink $SINK_INDEX1
    pacmd list-sink-inputs | awk '/index:/{print $2}' | xargs -r -I{} pacmd move-sink-input {} $SINK_INDEX1
fi
0

I think there is one more option worth mentioning and it's available at the official page for FAQ about PulseAudio at freedesktop.org. Under the following Title:

How do I switch the default sound card, moving all applications?

They provide the following script to do so:

#/bin/bash
# paswitch 2011-02-02 by Ng Oon-Ee <[email protected]>
# I can't remember where I found this script, can't locate the original author.
# Please inform me if you know, so that I can give proper attribution.
# CHANGES: Added auto-move all inputs to new default sound card.
# WAS: Pulse Audio Sound Card Switcher v1.0 2010-01-13
#   Switches between soundcards when run. All streams are moved to the new default sound-card.

# $totalsc: Number of sound cards available
totalsc=$(pacmd "list-sinks" | grep card: | wc -l) # total of sound cards: $totalsc
if [ $totalsc -le 1 ]; then # Check whether there are actually multiple cards available
  notify-send -u critical -t 5000 "Nothing to switch, system only has one sound card."
  exit
fi
# $scindex: The Pulseaudio index of the current default sound card
scindex=$(pacmd list-sinks | awk '$1 == "*" && $2 == "index:" {print $3}')
# $cards: A list of card Pulseaudio indexes
cards=$(pacmd list-sinks | sed 's|*||' | awk '$1 == "index:" {print $2}')
PICKNEXTCARD=1 # Is true when the previous card is default
count=0 # count of number of iterations
for CARD in $cards; do
  if [ $PICKNEXTCARD == 1 ]; then
# $nextsc: The pulseaudio index of the next sound card (to be switched to)
    nextsc=$CARD
    PICKNEXTCARD=0
# $nextind: The numerical index (1 to totalsc) of the next card
    nextind=$count
  fi
  if [ $CARD == $scindex ]; then # Choose the next card as default
    PICKNEXTCARD=1
  fi
  count=$((count+1))
done
pacmd "set-default-sink $nextsc" # switch default sound card to next

# $inputs: A list of currently playing inputs
inputs=$(pacmd list-sink-inputs | awk '$1 == "index:" {print $2}')
for INPUT in $inputs; do # Move all current inputs to the new default sound card
  pacmd move-sink-input $INPUT $nextsc
done
# $nextscdec: The device.description of the new default sound card
# NOTE: This is the most likely thing to break in future, the awk lines may need playing around with
nextscdesc=$(pacmd list-sinks | awk '$1 == "device.description" {print substr($0,5+length($1 $2))}' \
                         | sed 's|"||g' | awk -F"," 'NR==v1{print$0}' v1=$((nextind+1)))
notify-send "Default sound-card changed to $nextscdesc"
exit
# Below text was from original author and remains unaltered
# CC BY - creative commons
# Thanks God for help :) and guys lhunath, geirha, Tramp and others from irc #bash on freenode.net
0

My way to change the default audio output device was to write python script:

import os

os.system(f"pacmd set-default-sink {next(a for a in list(os.popen('pacmd list-sinks | grep index').readlines()) if '*' not in a).split(': ')[1][0]}")

I think you can modify it for your needs.

0

I'll add my little fish shell script to the mix.

#!/usr/bin/env fish

set sinks (pacmd list-sinks | pcregrep -Mo '(?<=index: )\d|(?<=state: )\w+' | sed 'N;s/\n/:/')
set switchtnow false
while true
    for sink in $sinks
        if $switchtnow
            set i (echo "$sink" | grep -Po '\d')
            pacmd set-default-sink "$i"
            return
        end

        set match (echo $sink | grep -o RUNNING)
        if  [ "$match" = "RUNNING" ]
            set switchtnow true
        end
    end
end
0

Here's a script generated with the help of ChatGPT to switch the default audio device using rofi...

#!/usr/bin/env python

import subprocess
import pulsectl

def get_devices_with_input_and_output():
    with pulsectl.Pulse('set-default-sink') as pulse:
        sinks = pulse.sink_list()
    with pulsectl.Pulse('set-default-source') as pulse:
        sources = pulse.source_list()

    devices_with_input_and_output = []
    for sink in sinks:
        for source in sources:
            if sink.description == source.description:
                devices_with_input_and_output.append((sink, source))

    return devices_with_input_and_output

def switch_default_audio_device(device_index):
    devices = get_devices_with_input_and_output()

    if device_index >= 0 and device_index < len(devices):
        input_device = devices[device_index][1]
        with pulsectl.Pulse('set-default-source') as pulse:
            pulse.source_default_set(input_device)

        output_device = devices[device_index][0]
        with pulsectl.Pulse('set-default-sink') as pulse:
            pulse.sink_default_set(output_device)

def select_device(devices):
    device_list = [f"{device[0].description}" for device in devices]
    rofi_cmd = ['rofi', '-dmenu', '-p', 'Select audio device', '-i', '-format', 'i']
    p = subprocess.Popen(rofi_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8')
    stdout, _ = p.communicate('\n'.join(device_list))

    if p.returncode == 0:
        try:
            device_index = int(stdout.strip())
            return device_index
        except ValueError:
            pass

    return None

if __name__ == '__main__':
    devices = get_devices_with_input_and_output()

    if not devices:
        print('No devices found with both audio input and output.')
    else:
        print('Devices with audio input and output:')
        for i, (sink, source) in enumerate(devices):
            print(f'{i + 1}. {sink.description}')

        device_index = select_device(devices)
        # output_device_index = select_device(devices)

        switch_default_audio_device(device_index)
        print('Default audio devices switched successfully.')

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .