How to use Jack audio in a meeting, with Chromium?

I want to produce a live feed into a meeting, using OBS as if it were any other live broadcast. The difference, of course, is that the viewers can talk back, in real time.

I see that OBS and Chromium are both unaware of Jack, but they do use PulseAudio. So I made some bridges between the two so that I could still use my Jack-routed local processing.
(/etc/pulse/default.pa is below)

OBS works perfectly well with that setup, and I can get OBS’s video output into Jitsi as a virtual camera. That’s fine. But Jitsi’s audio selection in Chromium seems to have some issues. The mic selection seems to do nothing at all, as if the function returns immediately, and the speaker selection causes that menu to hang.

I don’t know if this is a problem with Jitsi, or with Chromium. I’ve tried Firefox as well, but its media selection is unusably bad for this application. (It doesn’t tell me which generic name is what signal, and I can’t correct a wrong guess. So I didn’t even get to this point in FF.) Chromium at least shows me which is what, by a camera preview and an audio meter for each selection…or at least it’s supposed to. The mic meters for these Jack-to-PA sources also don’t seem to work for me. Perpetual silence, even though I know there’s something there.

Here’s a demo:


/etc/pulse/default.pa:

#!/usr/bin/pulseaudio -nF
#
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.

# This startup script is used only if PulseAudio is started per-user
# (i.e. not in system mode)

.fail

### Automatically restore the volume of streams and devices
load-module module-device-restore
load-module module-stream-restore
load-module module-card-restore

### Automatically augment property information from .desktop files
### stored in /usr/share/application
load-module module-augment-properties

### Should be after module-*-restore but before module-*-detect
load-module module-switch-on-port-available

### Use hot-plugged devices like Bluetooth or USB automatically (LP: #1702794)
.ifexists module-switch-on-connect.so
load-module module-switch-on-connect
.endif

### Load audio drivers statically
### (it's probably better to not load these drivers manually, but instead
### use module-udev-detect -- see below -- for doing this automatically)
#load-module module-alsa-sink
#load-module module-alsa-source device=hw:1,0
#load-module module-oss device="/dev/dsp" sink_name=output source_name=input
#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input
#load-module module-null-sink
#load-module module-pipe-sink
load-module module-jack-sink client_name=AZ_Out0 channels=2
load-module module-jack-sink client_name=AZ_Out1 channels=2
load-module module-jack-sink client_name=AZ_Out2 channels=2
load-module module-jack-sink client_name=AZ_Out3 channels=2
load-module module-jack-source client_name=ZA_In0 channels=2
load-module module-jack-source client_name=ZA_In1 channels=2
load-module module-jack-source client_name=ZA_In2 channels=2
load-module module-jack-source client_name=ZA_In3 channels=2

### Automatically load driver modules depending on the hardware available
.ifexists module-udev-detect.so
load-module module-udev-detect
.else
### Use the static hardware detection module (for systems that lack udev support)
load-module module-detect
.endif

### Automatically connect sink and source if JACK server is present
.ifexists module-jackdbus-detect.so
.nofail
load-module module-jackdbus-detect channels=2
.fail
.endif

### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-policy.so
load-module module-bluetooth-policy
.endif

.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover
.endif

### Load several protocols
.ifexists module-esound-protocol-unix.so
load-module module-esound-protocol-unix
.endif
load-module module-native-protocol-unix

### Network access (may be configured with paprefs, so leave this commented
### here if you plan to use paprefs)
#load-module module-esound-protocol-tcp
#load-module module-native-protocol-tcp
#load-module module-zeroconf-publish

### Load the RTP receiver module (also configured via paprefs, see above)
#load-module module-rtp-recv

### Load the RTP sender module (also configured via paprefs, see above)
#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 sink_properties="device.description='RTP Multicast Sink'"
#load-module module-rtp-send source=rtp.monitor

### Load additional modules from GSettings. This can be configured with the paprefs tool.
### Please keep in mind that the modules configured by paprefs might conflict with manually
### loaded modules.
.ifexists module-gsettings.so
.nofail
load-module module-gsettings
.fail
.endif


### Automatically restore the default sink/source when changed by the user
### during runtime
### NOTE: This should be loaded as early as possible so that subsequent modules
### that look up the default sink/source get the right value
load-module module-default-device-restore

### Make sure we always have a sink around, even if it is a null sink.
load-module module-always-sink

### Honour intended role device property
load-module module-intended-roles

### Automatically suspend sinks/sources that become idle for too long
load-module module-suspend-on-idle

### If autoexit on idle is enabled we want to make sure we only quit
### when no local session needs us anymore.
.ifexists module-console-kit.so
load-module module-console-kit
.endif
.ifexists module-systemd-login.so
load-module module-systemd-login
.endif

### Enable positioned event sounds
load-module module-position-event-sounds

### Cork music/video streams when a phone stream is active
load-module module-role-cork

### Block audio recording for snap confined packages unless they have
### the "pulseaudio" or "audio-record" interfaces plugged.
.ifexists module-snap-policy.so
load-module module-snap-policy
.endif

### Modules to allow autoloading of filters (such as echo cancellation)
### on demand. module-filter-heuristics tries to determine what filters
### make sense, and module-filter-apply does the heavy-lifting of
### loading modules and rerouting streams.
load-module module-filter-heuristics
load-module module-filter-apply

### Make some devices default
#set-default-sink output
#set-default-source input

I think it might be possible to send jack audio to the meeting after some customizations in jitas. Worth a try if it suits your use case.

  • Debian 11 Bullseye will be better since it has a newer version of jacktrip. Old version has some routing issues

  • jack audio must be directed to the default speaker. jitas uses it as a default mic in Chrome session

I’m not sure that it suits my use case. Mostly because I still don’t know what Jitas is or how to use it, even after reading the Readme. I did see though, that it launches right into installing a dedicated server, which is probably not feasible in my case.

Everything that I do needs to be on one local machine, including the “broadcast” production, extra audio processing, whatever the meeting does, and the recording. No switching in Jitsi, as that’s already done as part of the “broadcast”; including cameras, graphics, audio, and everything. Everyone else just logs into the meeting as usual; nothing special for them at all.

(It’s been done ever since TV existed, to have a live stage show with cameras to broadcast the show and its local audience to another, non-interactive audience. But I’m taking the broadcast out of its auxiliary role to what is otherwise an ordinary stage show, and putting it between the action and the audience. It’s still broadcast, using similar tools and mindset, and it’s still a live audience in the sense that they can respond back to the show, but the live response is via webcams at home instead of from seats in a theater. Or to think of it another way, I’m adding a live audience, via webcams at home, to a studio show.)

Besides, this issue has all the looks and feels to me of a bug that needs fixing, and when the bug is fixed, what I have now will work. The bug might actually be in Chromium, or it might be in Jitsi - I only know that the combination does this - but to have an inoperative menu item is proof enough that something is wrong. Even worse to hang when a menu item is selected.

According to this, I already have that:

$ cat /etc/debian_version
bullseye/sid
$ 

Based on an answer here:

I’m explicitly avoiding the default devices in my setup because I also have some difficulty in getting Jack to play nice with them. It seems that Jack needs to bind to a hardware device, which then becomes its system device with capture and playback endpoints, and it sync’s its sample rate to that device while resampling all the others. But on my machine, it also can’t use that device. Don’t know why. No errors, just silence.

So I bind it to the built-in speakers and mic that I’m not using anyway for this, leave those system points disconnected, then use alsa_in and alsa_out in the startup script to make my USB soundcard available to it as well, and connect to those endpoints instead.

That works, except for Jitsi in Chromium not being able to select a Jack to PA bridge device like OBS can. All of OBS’s audio connections are to those bridges, and then Jack connects them to the actual hardware via some additional processing. I want to do the same thing with Jitsi’s audio.

I tested on my Debian 11 Bullseye machine and it works for me. These are the steps what I did. The order is important.

packages

as root

apt-get --no-install-recommends install jackd2 alsa-utils mpg321

snd-aloop module

as root

modprobe snd-aloop enable=1,1 id=aloop100,aloop101
ls /proc/asound

virtual microphone

pacmd load-module module-remap-source source_name=mic100 \
    master=alsa_input.platform-snd_aloop.0.analog-stereo \
    source_properties=device.description=mic100 channels=2
pactl list sources short | grep mic100

chromium

Open chromium and select mic100 as a microphone

jackd

jackd -R -d dummy -r44100 -C 0 -P 0 &

jack playback slot for virtual microphone

alsa_out -j mic100 -d hw:aloop100,1 &

test audio

mpg321 -o alsa -a hw:aloop101,0 my-audio-file.mp3

second jack device

This will simulate another jack device

alsa_in -j input101 -d hw:aloop101,1 &
jack_connect input101:capture_1 mic100:playback_1
jack_connect input101:capture_2 mic100:playback_2
aaron@aaron-Lubuntu:~$ sudo apt --no-install-recommends install jackd2 alsa-utils mpg321
[sudo] password for aaron: 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
jackd2 is already the newest version (1.9.12~dfsg-2ubuntu2).
jackd2 set to manually installed.
alsa-utils is already the newest version (1.2.2-1ubuntu2.1).
alsa-utils set to manually installed.
Recommended packages:
  libaudio-scrobbler-perl
The following NEW packages will be installed:
  mpg321
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 43.2 kB of archives.
After this operation, 121 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://us.archive.ubuntu.com/ubuntu focal/universe amd64 mpg321 amd64 0.3.2-3ubuntu1 [43.2 kB]
Fetched 43.2 kB in 0s (97.5 kB/s) 
Selecting previously unselected package mpg321.
(Reading database ... 289772 files and directories currently installed.)
Preparing to unpack .../mpg321_0.3.2-3ubuntu1_amd64.deb ...
Unpacking mpg321 (0.3.2-3ubuntu1) ...
Setting up mpg321 (0.3.2-3ubuntu1) ...
Processing triggers for man-db (2.9.1-1) ...
aaron@aaron-Lubuntu:~$ sudo modprobe snd-aloop enable=1,1 id=aloop100,aloop101
aaron@aaron-Lubuntu:~$ ls /proc/asound
aloop100  aloop101  Capture  card0  card1  card2  card3  card4  card5  cards  devices  hwdep  modules  NVidia  oss  PCH  pcm  seq  timers  U192k  version
aaron@aaron-Lubuntu:~$ pacmd load-module module-remap-source source_name=mic100 master=alsa_input.platform-snd_aloop.0.analog-stereo source_properties=device.description=mic100 channels=2
aaron@aaron-Lubuntu:~$ pactl list sources short | grep mic100
15      mic100  module-remap-source.c   s16le 2ch 44100Hz       SUSPENDED
aaron@aaron-Lubuntu:~$ 

At this point, it appears in the Mics list like all the others, but selecting it also does the same thing as the others. No response and no hang, as if the function returns immediately.


Note: this screenshot is without my usual startup script, so a few other things don’t necessarily work either because of that.

After a while though, it does decide to visibly select it, on its own, and pactl list sources short shows it as RUNNING now, instead of SUSPENDED. But that wasn’t immediate when clicked.

Moving on then (most of my library is FLAC, but I did find some MP3’s):

aaron@aaron-Lubuntu:~$ jackd -R -d dummy -r44100 -C 0 -P 0 &
[1] 17055
aaron@aaron-Lubuntu:~$ jackdmp 1.9.12
Copyright 2001-2005 Paul Davis and others.
Copyright 2004-2016 Grame.
Copyright 2016-2017 Filipe Coelho.
jackdmp comes with ABSOLUTELY NO WARRANTY
This is free software, and you are welcome to redistribute it
under certain conditions; see the file COPYING for details
Cannot create RT messagebuffer thread: Operation not permitted (1)
Retrying messagebuffer thread without RT scheduling
Messagebuffer not realtime; consider enabling RT scheduling for user
no message buffer overruns
Cannot create RT messagebuffer thread: Operation not permitted (1)
Retrying messagebuffer thread without RT scheduling
Messagebuffer not realtime; consider enabling RT scheduling for user
no message buffer overruns
Cannot create RT messagebuffer thread: Operation not permitted (1)
Retrying messagebuffer thread without RT scheduling
Messagebuffer not realtime; consider enabling RT scheduling for user
no message buffer overruns
`default' server already active
Failed to open server
alsa_out -j mic100 -d hw:aloop100,1 &
[2] 17064
[1]   Exit 255                jackd -R -d dummy -r44100 -C 0 -P 0
aaron@aaron-Lubuntu:~$ Cannot lock down 82280346 byte memory area (Cannot allocate memory)
Rate doesn't match (requested 48000Hz, get 44100Hz)
Setting of hwparams failed: Invalid argument
mpg321 -o alsa -a hw:aloop101,0 "/media/aaron/WinShare/Users/Aaron/Music/01_Music/Apologetix/Singles/OrchardAvenue/04-Come To Father.mp3"
High Performance MPEG 1.0/2.0/2.5 Audio Player for Layer 1, 2, and 3.
Version 0.3.2-1 (2012/03/25). Written and copyrights by Joe Drew,
now maintained by Nanakos Chrysostomos and others.
Uses code from various people. See 'README' for more!
THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR OWN RISK!
Title   : Come To Father                 Artist : ApologetiX                    
Album   : Orchard Avenue                 Year    : 2012
Comment :                                Genre : Christian Parody - Single     

Directory: /media/aaron/WinShare/Users/Aaron/Music/01_Music/Apologetix/Singles/OrchardAvenue
Playing MPEG stream from 04-Come To Father.mp3 ...
MPEG 1.0 layer III, 320 kbit/s, 44100 Hz stereo
^C                                               <- (No meter in Chromium)
[0:57] Decoding of 04-Come To Father.mp3 finished.
[2]+  Exit 20                 alsa_out -j mic100 -d hw:aloop100,1
aaron@aaron-Lubuntu:~$ alsa_in -j input101 -d hw:aloop101,1 &
[1] 17165
aaron@aaron-Lubuntu:~$ Cannot lock down 82280346 byte memory area (Cannot allocate memory)
selected sample format: float
Cannot use real-time scheduling (RR/5)(1: Operation not permitted)
JackClient::AcquireSelfRealTime error
delay = 3825
delay = 1020
jack_connect input101:capture_1 mic100:playback_1
Cannot lock down 82280346 byte memory area (Cannot allocate memory)
ERROR mic100:playback_1 not a valid port
aaron@aaron-Lubuntu:~$ delay = 2050
delay = 1022
jack_connect input101:capture_2 mic100:playback_2
Cannot lock down 82280346 byte memory area (Cannot allocate memory)
ERROR mic100:playback_2 not a valid port
aaron@aaron-Lubuntu:~$ delay = 2091
jack_connect input101:capture_1 mic100:playback_1delay = 999
                       mpg321 -o alsa -a hw:aloop101,0 "/media/aaron/WinShare/Users/Aaron/Music/01_Music/Apologetix/Singles/OrchardAvenue/04-Come To Father.mp3"
High Performance MPEG 1.0/2.0/2.5 Audio Player for Layer 1, 2, and 3.
Version 0.3.2-1 (2012/03/25). Written and copyrights by Joe Drew,
now maintained by Nanakos Chrysostomos and others.
Uses code from various people. See 'README' for more!
THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR OWN RISK!
Title   : Come To Father                 Artist : ApologetiX                    
Album   : Orchard Avenue                 Year    : 2012
Comment :                                Genre : Christian Parody - Single     

Directory: /media/aaron/WinShare/Users/Aaron/Music/01_Music/Apologetix/Singles/OrchardAvenue
Playing MPEG stream from 04-Come To Father.mp3 ...
MPEG 1.0 layer III, 320 kbit/s, 44100 Hz stereo
Can't open libao driver with device hw:aloop101,0 (is device in use?)
aaron@aaron-Lubuntu:~$ delay = 2050
delay = 1023
delay = 2049
delay = 1021
delay = 2050
delay = 1013
delay = 2054
delay = 999
delay = 2059
delay = 993
delay = 2049
delay = 1023
delay = 2049
delay = 1023
delay = 2070
delay = 1016
delay = 2054
delay = 1019
delay = 2057
delay = 1019
delay = 2061
delay = 1020
delay = 2056
delay = 1011
delay = 2109
delay = 1016

I get the impression that things are falling apart here, but I kept going anyway because I’m much more comfortable with the GUI and it might actually be okay here. (?)


It might also be helpful to include my startup scripts, which were running when I recorded the demo:

Main (double-click this file, and Execute in Terminal):

#!/bin/bash



cd $(dirname $0)



JACK_SNAPSHOT=./Meeting.jack_snapshot

MIC0=hw:CARD=U192k,DEV=0
SPK0=hw:CARD=U192k,DEV=0



echo
echo "Looking for MIC0: $MIC0"
until arecord -L | grep "$MIC0" > /dev/null
do
    sleep 1
done
echo "Found MIC0: $MIC0"

echo
echo "Looking for SPK0: $SPK0"
until aplay -L | grep $SPK0 > /dev/null
do
    sleep 1
done
echo "Found SPK0: $SPK0"



# Jack
echo
echo "Start Jack..."
qjackctl &
sleep 2

# Save current connections
echo
echo "Save Jack Connections"
aj-snapshot --jack --force "$JACK_SNAPSHOT"

# Mic(s)
echo
echo "Use MIC0: $MIC0"
alsa_in  -j MIC0 -d $MIC0 -c 2 -r 48000 &
PID_MIC0=$!

# Speakers
echo
echo "Use SPK0: $SPK0"
alsa_out -j SPK0 -d $SPK0 -c 2 -r 48000 &
PID_SPK0=$!

# Delete all connections
echo
echo "Clear Jack Connections"
aj-snapshot --jack --remove

# Audio Processing
echo
echo "Start Audio Processing..."
carla ./Meeting.carxp &
PID_PROCESSING=$!
sleep 5

# OBS Master (feed to meeting)
echo
echo "Start OBS Master (feed to meeting)..."
obs-studio --disable-updater --multi --studio-mode --profile "Meeting_Master" --collection "Meeting_Master" --scene "Camera - Camcorder" --startvirtualcam &
PID_MASTER=$!
sleep 5

# OBS Slave (local display and recording)
echo
echo "Start OBS Slave (local display and recording)..."
obs-studio --disable-updater --multi --studio-mode --profile "Meeting_Slave" --collection "Meeting_Slave" --scene "Meeting" &
PID_SLAVE=$!
sleep 5

# Meeting
echo
echo "Start Meeting..."
chromium https://meet.jit.si/AaronD_test &
PID_MEETING=$!
sleep 2
xdotool search --sync --classname "Chromium" windowmove --sync 0 1080
sleep 2



# Auto-switch and instructions
echo
echo "Wait for Meeting Done..."
./Master_Slave.py
echo



echo
echo "Unuse SPK0: $SPK0..."
kill -INT "$PID_SPK0"

echo
echo "Unuse MIC0: $MIC0..."
kill -INT "$PID_MIC0"

# Restore previous connections
echo
echo "Restore Previous Jack Connections"
aj-snapshot --jack --remove --restore "$JACK_SNAPSHOT"



# Extra line for readability
echo



read -p "Press enter to exit"

Master_Slave.py:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re
import subprocess

from obswebsocket import obsws, events, requests


wsSlv = obsws("localhost", 4445, "********")
wsSlv.connect()


def on_switch(message):
    name = message.getSceneName()
    if re.match("Camera.*", name):
        wsSlv.call(requests.SetCurrentScene("Meeting"))
    if re.match("Feature.*", name):
        wsSlv.call(requests.SetCurrentScene("Feature"))
    if re.match("Voiceover.*", name):
        wsSlv.call(requests.SetCurrentScene("Voiceover"))


wsMst = obsws("localhost", 4444, "********")
wsMst.register(on_switch, events.SwitchScenes)
wsMst.connect()


subprocess.run(["zenity", "--info", "--width=350", "--title=DO NOT CLOSE THIS!!!", "--text=When the meeting is done:\n1. Hang it up, and close it\n2. Exit button in both OBS\n3. Close everything else\n4. Click OK here\n\nEverything else will then clean up automatically."])


# Disconnect from both, if possible
# If not, don't worry about it; it probably is already

try:
    wsMst.disconnect()
except:
    pass

try:
    wsSlv.disconnect()
except:
    pass

According to the output, the “jackd -R -d dummy -r44100 -C 0 -P 0 &” command cannot run because there is already a running instance

I wouldn’t think that would be a problem, right? If there’s already one running, then this one would fail and everything else would connect to the running one. So it should work anyway if everything else was right. But it doesn’t, so there must be something else wrong.


I had always thought that Jack wasn’t running by default, until something started it. So I have the graphical QjackCtl set to start it when it starts itself. Then the script starts qjackctl. After that point in the script, I do have it running, but not before.

I didn’t do that before trying the above: just a cold boot, and then copy/paste commands. So if it was running already, then it had to have been automatic on startup, and my assumption was wrong.

Here are my settings for Jack as I understand them (Menu → Sound & Video → QjackCtl, then Setup… button):



(hw:PCH is the laptop’s built-in soundcard. There are many others, like NVidia for HDMI audio (lots of those) and U192k for my USB soundcard. No dummy, but there is a (default), which I assume to be a choice by something else, out of the other options here.)


The sample rate is important. If there is an already running one with different sample rate, alsa_in and alsa_out will fail as seen in the log

Rate doesn't match (requested 48000Hz, get 44100Hz)

Another point, if alsa_in and alsa_out are initialized when the related sources or destinations are not active, they set their defaults (format, sample rate etc) and if these don’t match with the device, it is not possible to get sound

Going back to the original problem, and seeing that all the rest of my rig works - the meters for all of the other processing and in OBS follow the mics and local playback as expected - then all of that must be good. A failure in Jack or its connections would prevent at least some of that from working, as every connection between everything goes through Jack. And all of that works.

Since only Chromium has a problem at all, then according to what you said above, the sample rate in Chromium must be fixed at 44.1k and neither side of that Jack-PulseAudio bridge can accommodate it. Is that true? I don’t think so, given this additional info, some of which is mentioned in the original demo:


Additional Info:

There’s also a Windows version of this rig, on this dual-boot machine, and it has the exact same problem. It doesn’t use Jack at all, or any of the plugins that I have on Linux, but a shareware program (VB-Audio VoiceMeeter Potato) that does its own processing between a combination of real and virtual sound cards. Nevertheless, Chrome on that has the exact same symptoms as Chromium does on Linux. So I don’t think it has anything to do with Jack, unless the important settings happen to be the same and trigger the same problem independently.

I also looked at a different, Windows-only machine, and I could get that one to work. Same shareware program with its virtual sound cards, also set to 48kHz, but the audio selection in Chrome on that one “just works”. I don’t know what the difference is. Win10 Pro for both.

Unfortunately, that Windows machine that works, is not available most of the time (I normally wouldn’t have it today either), whereas this dual-boot one is all mine. And I’d much rather use Linux anyway, given the choice (Lubuntu 20.04 LTS), because of all the processing that I have here already and Jack’s freeform routing between them. I just mention the two Windows attempts as additional datapoints.

Having taken a break and come back, that looks like a red herring. The problem already occurs BEFORE that point, so it must not have anything to do with it. In fact, since that’s the first thing you do with Jack, and the problem already occurs by then, it tells me that the problem is not with Jack at all. We’ve completely replicated the problem without Jack, as described in my replies above.

Any ideas what it might be?

I also opened a ticket with Chromium itself, and after getting bounced around a bit between developers, one of them said:

We don’t implement device changes on Linux since PulseAudio normally takes care of that for us. Not sure how it’s supposed to work w/ jack.

So I guess it’s really a Chromium-on-Linux problem, and nothing to do with Jitsi at all. And at least one of the Chromium people says that the API that Jitsi uses for its audio selection menu, is not supposed to work on Linux because PulseAudio has its own selection instead. That’s a bummer.

But it does work to use pavucontrol-qt to make the selection instead, AND DON’T TOUCH JITSI’S AUDIO MENU! Trying to use Jitsi’s menu still causes problems, but the options on the Playback and Recording tabs of pavucontrol-qt do the job just fine.