Calls with USB headsets and other audio devices

Attempting to use Calls with a USB headset (or other USB audio devices) attached to the Librem 5 presents me with a couple of related issues…

  1. When the call connects, the input (headset mic) volume drops to a very low level, so low that the call recipient can either barely hear you or simply can’t hear you at all.
  2. Raising the input volume manually from g-c-c invariably triggers automatic gain control which then steps up the volume to 100% or close to it over a short period of time resulting in the volume being too loud at the recipient’s end to such an extent the audio becomes unclear.

I had a cumbersome hack workaround which I detailed in the Internal mic fix? thread, this works but is not practical for everyday usage.

I spent some more time on it and have come up with a “set and forget” solution which allows for USB headsets to be the simple plug ‘n’ play devices they’re supposed to be while being able to be used by Calls without having their input volumes messed around with.

I’m using udev to detect the devices being attached, this also triggers a systemd user unit which in turn adds a filter definition to the device properties. It’s the same filter definition as is added to the internal audio hardware at boot so when a call connects the echo-cancel module is loaded, the filter is applied and input volumes remain unchanged.

The files required will be placed in system locations so you will require root permissions. to write to those file locations

For udev, I create the file…

/etc/udev/rules.d/99-usb-audio-devices.rules

…with the following contents…

# USB audio devices requiring  echo cancellation filter definition
ACTION=="change", SUBSYSTEM=="sound", \
                  ENV{ID_TYPE}=="audio", \
                  ENV{ID_BUS}=="usb", \
                  ENV{SOUND_INITIALIZED}=="1", \
                  ENV{ID_SERIAL}=="?*", \
                  ENV{ECHO_CANCEL}!="0", \
                  TAG+="systemd", \
                  SYMLINK+="snd/audio-device_%E{ID_SERIAL}", \
                  ENV{SYSTEMD_USER_WANTS}="echo-cancel-filter@%E{ID_SERIAL}.service"

This triggers when a USB audio device is plugged in to the Librem 5 and in turn triggers the systemd user unit passing the “ID_SERIAL” value to the unit. From the devices I have tested with, they all use the “ID_SERIAL” string to form part of the card name as used by ALSA and pulseaudio so it appears to be a good choice to aid IDing the devices in the config script.

The udev rule includes a check for an “ECHO_CANCEL” value, this provides a mechanism for disabling the echo cancellation filter config on any devices if needs be.

For the systemd user unit, I create the file…

/etc/systemd/user/echo-cancel-filter@.service

…the “@” in the file name is important, and I place the following contents into the file…

[Unit]
Description=Echo cancellation filter for audio devices

Requiste=pulseaudio.service
After=pulseaudio.service
After=dev-snd-audio\x2ddevice_%i.device

[Service]
Type=oneshot
ExecStart=/usr/bin/echo-cancel %i

…the Requiste and Afters are probably redundant but “belt and braces” and all that. The %i is the “ID_SERIAL” string from the previous udev rule. This unit file just triggers the script and passes the “ID_SERIAL” value along to it, the script does the actual work of adding the filter parameters to the device.

For the script I create the file…

/usr/bin/echo-cancel

With the following contents…

#!/bin/bash

ECF_PARAMS='"'
ECF_PARAMS+='use_master_format=1 '
ECF_PARAMS+='aec_args=\"'
ECF_PARAMS+='analog_gain_control=0 '
ECF_PARAMS+='voice_detection=0 '
ECF_PARAMS+='high_pass_filter=0 '
ECF_PARAMS+='noise_suppression=0'
ECF_PARAMS+='\"'
ECF_PARAMS+='"'

for TYPES in sources sinks; do

  IDX=$(pactl list short ${TYPES} \
              | awk -v DAC="${1}" '$2 ~ DAC \
              && $2 !~ /\.monitor$/ \
              && $2 !~ /\.echo-cancel$/ { print $1 }')

  if [[ ! -z ${IDX} ]]; then
    pacmd update-${TYPES%?}-proplist ${IDX} \
            filter.apply.echo-cancel.parameters=${ECF_PARAMS}
  fi
done

… the script requires to be made executable…

sudo chmod +x /usr/bin/echo-cancel

The tricky part of the script was working out a formatting of the parameters string that pacmd would parse properly.

After rebooting, the rules will be in place and plugging in any USB audio device will now have the required filter definition automatically added and will be usable with Calls .

If you don’t want to reboot to test, you can simply reload the udev rules…

sudo udevadm control --reload-rules

So far I have tested with the following hardware…

These all work fine.

6 Likes

I tried your suggested setup with a USB headset attached (L5 included headset with USB adapter). There is no mic input in Calls, but it works OK when calling from Telegram, where the other party can hear me fine (except for the echo from the top microphone).

This is what the mic output from pactl list sources looks like:

Portar:
	analog-input-mic: Microphone (type: Mic, priority: 8700, availability unknown)
Aktiv port: analog-input-mic
Format:
	pcm

With your USB headset attached can you post the complete output from…

pactl list short sources

If the remote end is hearing anything from either of the internal mics it would suggest that the headset mic was not the default (selected) input. The headset mic should be selected as default automatically when it is connected, you can confirm this by checking the ‘Sound’ tab of the main setting application.

EDIT: Having not used Telegram myself, I am not aware of any audio settings it may offer. I would double check the Telegram settings for audio input/microphone if the setting exists and confirm it is/was set to use the headset mic.

For calls, if the remote end hears no audio from your side, the most likely issue is that the echo-cancellation filter has not been defined on the headset mic. Without the filter definition, the mic volume will get dropped to a very low level as soon as a call connects, so low that for most mics no audio will be heard at all. You can check this by, dialing a call and as it’s ringing, open or switch to the sound tab of the main settings application, when the call connects check if the volume slider also drops.

The script that runs when you connect an USB audio device should apply the filter, but it may not be identifying the correct source or too many sources. The output from pactl list short sources may help identify if that is an issue before we investigate further if needs be.

|0|alsa_output.platform-sound.HiFi__hw_L5_0__sink.monitor|module-alsa-card.c|s16le 2 kan. 48000 Hz|SUSPENDED|
|---|---|---|---|---|
|1|alsa_input.platform-sound.HiFi__hw_L5_0__source|module-alsa-card.c|s16le 2 kan. 48000 Hz|SUSPENDED|
|2|alsa_output.platform-sound-wwan.stereo-fallback.monitor|module-alsa-card.c|s16le 2 kan. 48000 Hz|SUSPENDED|
|3|alsa_input.platform-sound-wwan.stereo-fallback|module-alsa-card.c|s16le 2 kan. 48000 Hz|SUSPENDED|
|10|alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo.monitor|module-alsa-card.c|s16le 2 kan. 48000 Hz|IDLE|
|11|alsa_input.usb-GeneralPlus_USB_Audio_Device-00.mono-fallback|module-alsa-card.c|s16le 1 kan. 48000 Hz|IDLE|

I thought there may have been something in the card name that had tripped up the script but I don’t see anything there.

If you can look again at the output of the main sources list…

pactl list sources

If you look at the last few lines just above the output you provided in your initial post there should be a line that starts filter.apply.echo-cancel.parameters. For reference, here is a sample of with a USB headset attached, you are looking for the line 6th from the end…

Source #8
	State: SUSPENDED
	Name: alsa_input.usb-Solid_State_System_Co._Ltd._MMX_150-00.mono-fallback
	Description: MMX 150 Mono
	Driver: module-alsa-card.c
	Sample Specification: s16le 1ch 48000Hz
	Channel Map: mono
	Owner Module: 30
	Mute: no
	Volume: mono: 52428 /  80% / -5.81 dB
	        balance 0.00
	Base Volume: 19944 /  30% / -31.00 dB
	Monitor of Sink: n/a
	Latency: 0 usec, configured 0 usec
	Flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY 
	Properties:
		alsa.resolution_bits = "16"
		device.api = "alsa"
		device.class = "sound"
		alsa.class = "generic"
		alsa.subclass = "generic-mix"
		alsa.name = "USB Audio"
		alsa.id = "USB Audio"
		alsa.subdevice = "0"
		alsa.subdevice_name = "subdevice #0"
		alsa.device = "0"
		alsa.card = "2"
		alsa.card_name = "MMX 150"
		alsa.long_card_name = "Solid State System Co.,Ltd. MMX 150 at usb-xhci-hcd.5.auto-1, full speed"
		alsa.driver_name = "snd_usb_audio"
		device.bus_path = "platform-xhci-hcd.5.auto-usb-0:1:1.0"
		sysfs.path = "/devices/platform/soc@0/38100000.usb/xhci-hcd.5.auto/usb3/3-1/3-1:1.0/sound/card2"
		udev.id = "usb-Solid_State_System_Co._Ltd._MMX_150-00"
		device.bus = "usb"
		device.vendor.id = "2e50"
		device.vendor.name = "Solid State System Co.,Ltd."
		device.product.id = "0026"
		device.product.name = "MMX 150"
		device.serial = "Solid_State_System_Co._Ltd._MMX_150"
		device.string = "hw:2"
		device.buffering.buffer_size = "192000"
		device.buffering.fragment_size = "96000"
		device.access_mode = "mmap+timer"
		device.profile.name = "mono-fallback"
		device.profile.description = "Mono"
		device.description = "MMX 150 Mono"
		module-udev-detect.discovered = "1"
		device.icon_name = "audio-card-usb"
		filter.apply.echo-cancel.parameters = "use_master_format=1 aec_args=\"analog_gain_control=0 voice_detection=0 high_pass_filter=0 noise_suppression=0\""
	Ports:
		analog-input-mic: Microphone (type: Mic, priority: 8700, availability unknown)
	Active Port: analog-input-mic
	Formats:
		pcm

If you have the filter.apply.echo-cancel.parameters line, ensure that the headset microphone is selected as the default input from the sound tab of the main settings application and that the level meter below the volume shows some input when talking into the mic. Then, place a call, and check that the microphone input volume level when the call connects, let me know if it stays where it was set or drops.

If you don’t have the filter.apply.echo-cancel.parameters, but have the script in place, then try invoking the script manually by entering…

echo-cancel GeneralPlus_USB

It should complete with no errors or output, let me know if there is any output to the above command. If it completes without any errors or output, go back and check if the filter.apply.echo-cancel.parameters is now present.

Yes.

Yes and yes.

When I compare the last 6 lines of your pactl list output to mine, they look exactly the same.

With the mic volume on max in the main settings, I placed a call, but the other end could hear nothing. Same thing for an incoming call. Mic input volume level stayed at max while on the call.

This is not a situation I have encountered when testing.

The only other thing to look at would be to check that the required loopback and filter streams are being created during a call. With a call connected, from a terminal you can enter either…

pactl list

…or…

pa-info

Either of those will dump a load of information on all loaded modules, sinks, sources and streams, pa-info will dump all information including the the details of the complete pulseaudio environment, pa-info is the more comprehensive but output from either should be enough at this stage. The output is very verbose so it may be easier to dump it to a file which can be done from the terminal with…

pactl list > pactl.txt

…or

pa-info > pa-info.txt

If you can provide the output of the command or the contents of which ever .txt file you create I can have a look over it and see if the loopback and filter streams are being created or if there is anything else that stands out as being a potential issue.

Thank you for engaging in my problem! I am very grateful that you are working on this “set and forget” solution and would like to see it work as well for me as it does for you. I have the output from the two commands above during an active call in two different text files, but they are very long as you pointed out. Please tell me which passages could be of interest.

Thank you!

As it’s not a situation I have encountered myself, I can’t say with any certainty exactly what you should be seeing. I know that when a call connects the filter streams get created and there are a few loopback streams to route audio from the mic to the modem and from the modem to the speakers (or headphones), so I would initially be looking for mentions of ‘loopback’ and ‘echo cancel’ in an attempt to ensure the required streams are getting established and also that their volumes are set a a sensible level (these streams should all have volumes set at 100%), but there may be other config option(s) that are out causing the issue but I wouldn’t know what they may be, I was planning to just have a dig through the output to see if I could spot anything elese that may be a potential issue.

It is possible that simply resetting your pulseaudio config back to absolute default will resolve the issue, this resolved some issues someone else was having with no sound from their internal mic.

To reset your pulseaudio config you just need to (re)move you current config and reboot, the config will be regenerated by pulseaudio on boot.

To move the current config out of the way, from a terminal…

mv ~/.config/pulse{,old}

Then reboot and a new default config will be created.

Leave your USB headset disconnected when rebooting so that the udev rule will pick it up and apply the required filters when you reattach it after reboot.

Good thinking, and small win! After that operation, the receiver can finally hear me from the USB headset. However complaining about the sound being rather muffled.

Good start though, now I need to figure out how to increase the clarity of the mic. Looking at the pactl list sources I get the following for the USB device sources (sorry for the Swedish):

Källa nr 10
	Tillstånd: SUSPENDED
	Namn: alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo.monitor
	Beskrivning: Monitor of USB Audio Device Analog stereo
	Drivrutin: module-alsa-card.c
	Samplingsspecifikation: s16le 2 kan. 48000 Hz
	Kanalmappning: front-left,front-right
	Ägarmodul: 35
	Tystad: nej
	Volym: front-left: 65536 / 100% / 0,00 dB,   front-right: 65536 / 100% / 0,00 dB
	       balans 0,00
	Basvolym: 65536 / 100% / 0,00 dB
	Övervakare för mottagare: alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo
	Latens: 0 µs, konfigurerad 0 µs
	Flaggor: DECIBEL_VOLUME LATENCY 
	Egenskaper:
		device.description = "Monitor of USB Audio Device Analog stereo"
		device.class = "monitor"
		alsa.card = "2"
		alsa.card_name = "USB Audio Device"
		alsa.long_card_name = "GeneralPlus USB Audio Device at usb-xhci-hcd.5.auto-1, full speed"
		alsa.driver_name = "snd_usb_audio"
		device.bus_path = "platform-xhci-hcd.5.auto-usb-0:1:1.0"
		sysfs.path = "/devices/platform/soc@0/38100000.usb/xhci-hcd.5.auto/usb3/3-1/3-1:1.0/sound/card2"
		udev.id = "usb-GeneralPlus_USB_Audio_Device-00"
		device.bus = "usb"
		device.vendor.id = "1b3f"
		device.vendor.name = "Generalplus Technology Inc."
		device.product.id = "2008"
		device.product.name = "USB Audio Device"
		device.serial = "GeneralPlus_USB_Audio_Device"
		device.string = "2"
		module-udev-detect.discovered = "1"
		device.icon_name = "audio-card-usb"
	Format:
		pcm

Källa nr 11
	Tillstånd: RUNNING
	Namn: alsa_input.usb-GeneralPlus_USB_Audio_Device-00.mono-fallback
	Beskrivning: USB Audio Device Mono
	Drivrutin: module-alsa-card.c
	Samplingsspecifikation: s16le 1 kan. 48000 Hz
	Kanalmappning: mono
	Ägarmodul: 35
	Tystad: nej
	Volym: mono: 52058 /  79% / -6,00 dB
	       balans 0,00
	Basvolym: 18471 /  28% / -33,00 dB
	Övervakare för mottagare: ej tillämpligt
	Latens: 0 µs, konfigurerad 40000 µs
	Flaggor: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY 
	Egenskaper:
		alsa.resolution_bits = "16"
		device.api = "alsa"
		device.class = "sound"
		alsa.class = "generic"
		alsa.subclass = "generic-mix"
		alsa.name = "USB Audio"
		alsa.id = "USB Audio"
		alsa.subdevice = "0"
		alsa.subdevice_name = "subdevice #0"
		alsa.device = "0"
		alsa.card = "2"
		alsa.card_name = "USB Audio Device"
		alsa.long_card_name = "GeneralPlus USB Audio Device at usb-xhci-hcd.5.auto-1, full speed"
		alsa.driver_name = "snd_usb_audio"
		device.bus_path = "platform-xhci-hcd.5.auto-usb-0:1:1.0"
		sysfs.path = "/devices/platform/soc@0/38100000.usb/xhci-hcd.5.auto/usb3/3-1/3-1:1.0/sound/card2"
		udev.id = "usb-GeneralPlus_USB_Audio_Device-00"
		device.bus = "usb"
		device.vendor.id = "1b3f"
		device.vendor.name = "Generalplus Technology Inc."
		device.product.id = "2008"
		device.product.name = "USB Audio Device"
		device.serial = "GeneralPlus_USB_Audio_Device"
		device.string = "hw:2"
		device.buffering.buffer_size = "192000"
		device.buffering.fragment_size = "96000"
		device.access_mode = "mmap+timer"
		device.profile.name = "mono-fallback"
		device.profile.description = "Mono"
		device.description = "USB Audio Device Mono"
		module-udev-detect.discovered = "1"
		device.icon_name = "audio-card-usb"
		filter.apply.echo-cancel.parameters = "use_master_format=1 aec_args=\"analog_gain_control=0 voice_detection=0 high_pass_filter=0 noise_suppression=0\""
	Portar:
		analog-input-mic: Microphone (type: Mic, priority: 8700, availability unknown)
	Aktiv port: analog-input-mic
	Format:
		pcm

It might be a little difficult to ascertain but are you sure that they are hearing the mic from your headset and not whatever the internal mics are picking up?

Looking at your source list, I see that the volume of the mic is set considerably higher than the base volume (79% vs 28%), I would recommend dropping and setting the volume closer to the base volume to start with then adjusting if required.

It may be that your device is reporting incorrect values, but if everything is correct, setting volumes greater than the base value would normally result is clipping and distortion this would result in very poor audio at the remote end.

It might be worth grabbing a sound sample of the USB headset mic with ‘Sound Recorder’ (gnome-sound-recorder) to check the quality of the source as the phone OS hears it locally. I’m not sure if that application is installed by default but I’m fairly certain it is adaptive so if not already installed it should be available from the PureOS store or command line (apt install gnome-sound-recorder).

Thanks, I’ll look into this when I get the chance!

Maybe also look at the alsamixer settings if need be.

I’ll be interested to hear how you get on with it.

It’s worth having a look at alsamixer, however, for the headsets I’ve looked at there wasn’t any additional control over the basic volume parameters.

I have been trying out my USB headset with the sound recorder. Unfortunately the audio quality is not good. As you pointed out, the input mic volume was set to around 80%. At that level, or actually even at lower and higher levels, I get a whining background sound with the L5 headphones and a 3.5mm to USB-C adapter. I tried another 3.5mm headset with the same adapter, and it was thankfully better. Not perfect, but way better. I suspecting that both the L5 headphones and my adapter are not of the highest quality. Guess I will have to acquire some higher quality USB-C headphones that don’t need an adapter to see if the quality can improve.

It might be worth posting the make and model of the adapter you have if they are known, just in case anyone else is looking at this, they’ll know to choose some other adapter.

I tested two USB-C to 3.5mm adapters at polar ends of the cost spectrum, the UGREEN USB C to 3.5mm adapter retails for around $10 USD, if that is available in your area it may be worth picking up that one. I thought the sound quality was pretty good in general but even more so considering it’s price point. I’ve heard some comments on build quality, all I can say is the one I have is fine.

I haven’t tested anything with the earbud/headset that is supplied with the phone, perhaps I should as I guess that is the most likely headset people will have access to.

If looking at USB-C to 3.5mm adapters there are a couple of things to look out for.

  • It has it’s own internal DAC
  • It is listed has having microphone/calls support

These criteria are not an absolute guaranteee that it’ll work with the Lbrem 5 or your headset but I would consider these the minimum that’ll have some possibility of working.

Not all DAC chips are equal either, the UGREEN adapter I have has a single BES3002 DAC chip but I hear UGREEN has a number of models all physically looking exactly the same with different DAC chips but nothing in the specifications to say exactly which chip is being used.

Actual direct USB headsets tend to be quite pricey for decent sound quality and just being USB may not be enough to guarantee compatibility with the Librem 5.

Finally a headset that seems to work with the above system settings!

Linocell (probably a private label for Swedish Kjell & Co) Roxcore Bullets - This one seems to work very well with the L5. No hissing or apparatus sound and about “as good as it gets” from my part according to my daughter.

My previous attempts were with this adapter:
Linocell USB-C to 3.5 mm adapter - together with the L5 3.5mm headphones produces a whining background sound. Together with another 3.5mm headset it was better. Not perfect, but way better.

That’s great news and happy to hear you have it working.

Having the details of the kit that is working for you will also be helpful for others, thanks.