Friday, February 14, 2014

HOWTO: Switch to HDMI audio out automatically on Linux

First things first, this is not a tutorial for getting your audio working over your HDMI if it isn't already. This assumes you already have audio over HDMI working via PulseAudio, but currently have to manually assign the new output after attaching your HDMI cable.

TL/DR

If you are using a Debian based distro and don't care about the details of how this works, then you can simply install the .deb file I've prepared here. After that finishes installing you should be good to go - attach your HDMI cable and the system should automatically switch from playing over the speakers/headphones to piping audio over the HDMI.

The Details or How we make things work on not Debian

How this works is a combination of PulseAudio, Bash, and a little bit of udev.

First things first, we need a bash script that with switch between PulseAudio outputs for us. Save the following bit of code to /usr/bin/hdmi_sound_toggle :

#!/bin/bash
#   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
  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
exit
Make our script executable with the command:

sudo chmod +x /usr/bin/hdmi_sound_toggle

Before moving on, confirm the script works as intended. While your HDMI cable is active run the command hdmi_sound_toggle and you should start hearing sound pipe out of the HDMI output.

Next, because PulseAudio is almost always run as a user level daemon, we need something that will move all users' Pulse sessions over to the new output when the HDMI is attached/detached. To do this we create the script /usr/bin/hdmi_sound_toggle_all with the contents:
#!/bin/bash

for dir in /home/*/
do
    dir=${dir%*/}
    sudo -u ${dir##*/} /usr/bin/hdmi_sound_toggle
done
Make this script executable as well:

sudo chmod +x /usr/bin/hdmi_sound_toggle_all

Confirm this script works as well, it needs to be run as root though, so run sudo hdmi_sound_toggle_all while your HDMI is attached - it should switch your output.

Finally, we need to create a udev rule that triggers our scripts when an HDMI cable is attached/detached. To do this we create the file /lib/udev/rules.d/hdmi_sound.rules with the contents:
SUBSYSTEM=="drm", ACTION=="change", RUN+="/usr/bin/hdmi_sound_toggle_all"
Note that having the full file path in the execute part is important. So if you placed your scripts somewhere other than /usr/bin like I recommended above - adjust this part accordingly.

You should be good to go, enjoy having audio over your HDMI happen automatically like it should by default. Have any questions, feel free to post a comment below and I'll do my best to help.

~Jeff Hoogland

21 comments:

  1. running the command hdmi_sound_toggle is not working. I'm an Ubuntu user.

    ReplyDelete
    Replies
    1. Can you toggle audio over your HDMI using another method?

      Delete
    2. When I run the command in Terminal I get this:
      /usr/bin/hdmi_sound_toggle: 16: [: 1: unexpected operator
      /usr/bin/hdmi_sound_toggle: 23: [: 0: unexpected operator
      /usr/bin/hdmi_sound_toggle: 16: [: 1: unexpected operator
      /usr/bin/hdmi_sound_toggle: 23: [: 1: unexpected operator
      Welcome to PulseAudio! Use "help" for usage information.
      >>> You need to specify a sink either by its name or its index.
      >>> Welcome to PulseAudio! Use "help" for usage information.
      >>> You need to specify a sink.

      Delete
    3. That implies there is something wrong with your bash. Did you copy the line #!/bin/bash at the top of the script?

      Delete
    4. Yes I did. and I tried to install the .deb package with the same rezult
      I think is a problem in the script to count/set/switch the sound card because if I run these commands:
      pacmd set-default-sink 1 & pacmd move-sink-input 2 1
      pacmd set-default-sink 0 & pacmd move-sink-input 2 0
      they toggle the output and is working.
      Unfortunately I do not know bash to bedbug this.

      Delete
    5. What about if you explicitly tell the system to run the script using bash.

      EG: bash /usr/bin/hdmi_sound_toggle

      That error message you are getting is from your bash being messed up.

      Delete
    6. ok using your command is working. Thanks!
      And now how do i make this script working without running this command in terminal?

      Delete
    7. Having #!/bin/bash as the first line of the script SHOULD tell your system to use bash to run this script. For whatever reason it isn't - you likely want to look into fixing why your system isn't handling bash files properly.

      Delete
    8. The command hdmi_sound_toggle_all is working also but is not trigged by udev rule that I created.

      Delete
    9. I maked an all day research. Your solution works just when attaching/detaching of HDMI cable triggers an UDEV event. in my case i have installed Nvidia drivers and they DON'T trigger an udev event (i don't even have an /sys/class/drm directory and that means that there is no drm SUBSYSTEM need to create a udev rule). I searched everywhere and I didn't found a good solution in my case. If someone has an idea I would be thankfully.

      Delete
  2. I've tried doing this, is whenn run it gives the message 'Welcome to PulseAudio! Use "help" for usage information.
    >>> >>> Welcome to PulseAudio! Use "help" for usage information' and my laptops speakers mute but the sound does not come via the TV. It's a new HDMI cable so I guess the next check is does it work with Windows.

    ReplyDelete
    Replies
    1. Likely means your hardware isn't being detected as an audio output device. Have you successfully piped audio over your HDMI on Linux before?

      Delete
    2. No I've not, I came across this post as top of my Google search.

      Delete
    3. Looks like you missed the first paragraph of the post then. You should always be sure to read a HOWTO in its entirety before you start blindly copy and pasting commands.

      Delete
  3. Glad you have the HDMI audio working for us all. I am using an acer c720 installed with bodhi 2.4.0 and I can't get the HDMI video Out to work. I pull HDMI to chromebook and the tv screen is balnk saying no signal. Without Chrome OS - video works with no issues.

    CAN ANYONE PLEASE HELP ME HDMI Video Out not working?? I really don't want to switch back to chrome os or re-installed Bodhi on a usb 3.0 flashdrive after I have all the software installed.

    ReplyDelete
    Replies
    1. After plugging in the HDMI cable open the "screen setup" tool and turn on the second screen.

      Delete
  4. Thank you so much for these scripts!
    It didn't work at first, but after some hours working on it, it finally works!

    For information (I'm on linux mint 16) :
    My HDMI cable doesn't correspond to the rule "SUBSYSTEM=="drm", ACTION=="change"
    But instead there is a connection and disconnection (both everytime!) when I plug/unplug it ...

    And i had to change the run cmd : RUN+="/bin/su my_user -c my_toggle_script"

    ReplyDelete
  5. The scripts work, but the udev rule does not trigger the scripts. Any suggestion? I'm running Manjaro Linux, most recent updates, kernel 4.3.

    ReplyDelete
    Replies
    1. udev must be messed up on your distro to not be triggering for this event. Might also be triggering a slightly different event name.

      Delete
    2. Actually, when looking in journalctl -b I found that udev does trigger the script but returns and exit code 1 error. If I run the script manually as [user] it works, but if I run it as [root] via su or sudo, it returns "No PulseAudio daemon running, or not running as session daemon." I seem to be having the issue of PulseAudio as a per user session that root cannot access and udev runs as root. Any suggestions?

      Delete