Default Audio Input (or Output) Device for MacOS
As a recent Apple convert, I enjoy a lot about Macbooks. My main gripe, however, is that anything I don’t appreciate is often difficult-to-impossible to fix to my liking.
A particular pain point for me has been around my wireless headphones (Sony WH-1000XM4, by the way) which come with a mediocre microphone built-in.
Usually, I wouldn’t care about a suboptimal headset microphone because the Macbook Pro microphone is perfectly fine for my needs. My ideal solution has previously been disabling the headset microphone at the operating system level. On Windows and Linux, this is usually a few clicks away.
On MacOS? No such equivalent. There is no way to set a default or preference order either.
MacOS generally prefers to switch to whatever input (or output) was most recently connected, and there is no way to configure that behavior. Ugh!
From my searches to find a way to tweak it to my liking (i.e. prefer the laptop’s microphone over the headset microphone), it seems like I’m far from alone.
I’ve come away unsatisfied by every solution I’ve found so far, so I ended up coming up with my own.
brew install switchaudio-osx⌗
As expected, there is a solution for managing input/output devices from the CLI. I figured that I could use it to watch the selected input device and switch it to the MacBook’s microphone whenever my headset’s microphone is selected.
SwitchAudioSource
provides a solution for both parts of the problem quite elegantly!
To follow along, install it via
brew install switchaudio-osx
Next, take a look at the available input devices. You should see some familiar names:
SwitchAudioSource -t input -a
# Output:
# Slava’s iPhone Microphone
# WH-1000XM4
# MacBook Pro Microphone
# Default microphone
The next two pieces of the puzzle will check the selected input device and update it conditionally. It looks like this as a one-liner:
# Switch to the built-in Microphone if the headset is selected as the input
[[ `SwitchAudioSource -t input -c` = "WH-1000XM4" ]] && \
SwitchAudioSource -t input -s "MacBook Pro Microphone"
Given that we can observe the command working well, we can wrap it in an infinite loop and throw it into an sh
file.
#!/bin/sh
while true;
do
[[ `/opt/homebrew/bin/SwitchAudioSource -t input -c` = "WH-1000XM4" ]] && \
/opt/homebrew/bin/SwitchAudioSource -t input -s "MacBook Pro Microphone" || \
sleep 1;
done
Registering a Service (Agent)⌗
Of course, the entire point of this exercise is convenience. Running a bash script in your terminal is not it. MacOS has an init service called launchd which allows us to run programs in the background on startup.
First, put the script file in /usr/local/bin/no-xm4-input-service.sh
. Feel free to use another name, but pay attention to the references.
Make sure it has the right permissions:
sudo chown root:wheel /usr/local/bin/no-xm4-input-service.sh
sudo chmod 755 /usr/local/bin/no-xm4-input-service.sh
Next, create a service file in ~/Library/LaunchAgents
. I called mine localhost.no-xm4-input.plist
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>localhost.no-xm4-input</string>
<key>Program</key>
<string>/usr/local/bin/no-xm4-input-service.sh</string>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Applying the correct permissions here is important, or else launchd will refuse to register the service:
sudo chown root:wheel ./localhost.no-xm4-input.plist
sudo chmod 640 ./localhost.no-xm4-input.plist
Lastly, you can register it to launchd via the following commands:
launchctl load ./localhost.no-xm4-input.plist
launchctl start localhost.no-xm4-input
You can confirm that it is running correctly via launchctl list
, the second number is the status code. It should be 0
:
launchctl list | grep localhost.no-xm4-input
# Output:
# 73035 0 localhost.no-xm4-input
Go Further⌗
You've Got Mail!
No more than five emails per year, all densely packed with knowledge.
Our service is extremely simple: a loop with a sleep. It doesn’t matter much in this case, but it’s not an ideal design. If you want to go further, consider that there is a small set of specific events that SwitchAudioSource
could be invoked in response to.
It’s possible to turn the service into something more event-driven. I suspect there might be interesting challenges here (mainly race conditions), which I didn’t want to spend time figuring out.
Other Solutions⌗
There are other approaches to it that users have reported to work. I’ll note them below for the souls that don’t want to start dabbling in their own launchd services.
MIDI Sheneningans⌗
The first solution I’ve encountered was configuring an aggregate input device in the built-in MIDI setup.
Supposedly, it’s a 3-step process:
- Create a new “Aggregate Device”
- Select the built-in Microphone within it
- Right-click the aggregate device and select it for input
This has previously worked for me on my M1, but did not on my M3 or M4 device. I’ve seen reports that it still works, but YMMV!
Software Solutions⌗
The top recommendation for software solutions I’ve seen was AirpodsSoundQualityFixer. I didn’t want to install yet another piece of software to fight with the OS, so I can’t speak to its quality.