External USB Speakerphone on OpenBSD

2021-12-15

This article describes some tests I did with a speakerphone under OpenBSD. I got it working with some restrictions.

Introduction

Within the winter term of 2021/22 I had the chance to get my hands on a portable, wireless speakerphone (EPOS 30T BT). A great opportunity to tinker with such kind of device under OpenBSD. Similar to jcs experience with bt audio[1], I used the include USB Bluetooth sound card to connect the speakerphone with my PC.

In case of success, I would use the device to replace my current USB audio interface/headset combination (yamaha AG 03 and Superlux HDC 660X). In general, I'm using this setup for video conferencing with my students. But first things first, I need to allow to record audio on my machine.

doas echo sysctl.kern.audio.record=1 >> /etc/sysctl.conf

Basic Setup

I don't want to reproduce the information from sndiod[2] and aucat[3]. What follows, therefore, is a summary of commands I used often during my tests.

Basically, all physical (the real hardware) audio devices get numbered like audio0, audio1 and corresponding (raw) sound devices rsnd0, rsnd1. On my system, rsnd0 refers to my internal audio device (e.g. speaker). To test an external audio device I first tried to play and record audio directly on the raw device rsnd/1 using aucat.

doas aucat -f rsnd/1 -o test.wav
doas aucat -f rsnd/1 -i test.wav

What I don't like about this approach is to execute these commands with higher privileges (i.e. root). For that reason, I configured sndiod in a way that it uses the internal audio device rsnd/0 but switches to rsnd/1 when available.

sndiod -dd -f rsnd/0 -F rsnd/1

Again, recording and playing of a .wav file but this time by using snd devices as regular user. But the speakerphone produces no sound (i.e. empty .wav file).

aucat -f snd/1 -o test.wav
aucat -f snd/1 -i test.wav

Side Node: On my system, I always suffered from "shuttering" audio on my speakers, my current workaround is to disable the rec mode on default:

sndiod -f rsnd/0 -m play -s default -F rsnd/1 -m play,rec 

Anyhow, the speakerphone keeps silent. So let's tinker with this thing a little bit more.

Testing

For testing, I attached my working yamaha AG 03 interface and start to invest. Here rsnd/1 is the EPOS and rsnd/2 the AG03. As you can see I disabled my internal sound system (damn is sndiod flexible!).

sndiod -dd -f rsnd/1 -f rsnd/2

At that point I came up with a test table as follows:

Device 	Mode 	Command

EPOS 	Record 	aucat -f snd/1 -o test.wav
EPOS 	Play 	aucat -f snd/1 -i test.wav
AG03 	Play 	aucat -f snd/2 -i test.wav
AG03 	Record 	aucat -f snd/2 -o test.wav

Resulting in the following combinations. (BTW. sndiod is really nice in doing such things in the mix)

Record 	Play 	Result

EPOS 	EPOS 	no audio
EPOS 	AG03 	no audio
AG03 	EPOS 	yes
AG03 	AG03 	yes

So it seems, that the speakerphone is not recording. As a next step I compared the monitor and control information of both devices to get a hint. After shutting down of sndiod to get access to the raw devices.

Control Information for the AG03 (rsnd/2):

In both cases, I've checked the following points:

Audio

audioctl -f /dev/audio2
name=uaudio1
mode=play
pause=0
active=0
nblks=16
blksz=480
rate=48000
encoding=s24le4msb
play.channels=2
play.bytes=0
play.errors=0
record.channels=2
record.bytes=0
record.errors=0

Mixer

mixerctl -f /dev/audio2
record.enable=sysctl

Control information for the EPOS

Audio

audioctl -f /dev/audio1
name=uaudio0
mode=play
pause=0
active=0
nblks=16
blksz=480
rate=48000
encoding=s16le
play.channels=2
play.bytes=0
play.errors=0
record.channels=1
record.bytes=0
record.errors=0

Mixer settings

mixerctl -f /dev/audioctl1
outputs.dac=128
inputs.record=255
inputs.record_mute=off
record.enable=sysctl

Sessions with sndiod

Maybe session output of sndiod brings more light into that. A regular session with sndiod looks like:

snd0.default: rec=0:1 play=0:1 vol=32768 dup
snd0.0: rec=0:1 play=0:1 vol=32768 dup
snd1.1: rec=0:1 play=0:1 vol=32768 dup
default/server.device=0:1 at 1 -> opt_dev:default/0: added
default/server.device=1:0 at 2 -> opt_dev:default/1: added
app/firefox0.level=127 at 3 -> slot_level:firefox0: added
warning, device opened in play-only mode
0/output0.level=128 at 4 -> hw:0/0: added
0/input0.level=255 at 5 -> hw:0/64: added
0/input0.mute=0 at 6 -> hw:0/96: added
snd0: 48000Hz, s16le, play 0:1, 16 blocks of 480 frames
firefox0: 48000Hz, s16le, play 0:1, 4 blocks of 480 frames
snd0: device started
firefox0: attached at -7680 + 0/480

Screening the logs, warning, device opened in play-only mode is interesting because even if I configured this device in play,rec mode it merely gets opened in play-only mode.

I'm not certain about the reason but had some ideas in mind:

The last mile with aucat

So back to start, I start recording with aucat like

aucat -f snd/0 -o test.wav

Corresponding sndiod configuration that excludes all other audio devices and just uses the speakerphone.

sndiod -dd -f rsnd/1

And the corresponding log:

snd0.default: rec=0:1 play=0:1 vol=32768 dup
snd0.0: rec=0:1 play=0:1 vol=32768 dup
warning, device opened in play-only mode
0/output0.level=128 at 1 -> hw:0/0: added
0/input0.level=255 at 2 -> hw:0/64: added
0/input0.mute=0 at 3 -> hw:0/96: added
snd0: 48000Hz, s16le, play 0:1, 16 blocks of 480 frames
default/server.device=0:1 at 4 -> opt_dev:default/0: added
app/firefox0.level=127 at 5 -> slot_level:firefox0: added
firefox0: 48000Hz, s16le, play 0:1, 4 blocks of 480 frames
snd0: device started
firefox0: attached at -7680 + 0/480
app/aucat0.level=127 at 6 -> slot_level:aucat0: added
aucat0: attached at -7680 + 0/480
aucat0: 48000Hz, s16le, rec 0:1, 20 blocks of 480 frames
aucat0: detached at 0 + 0/480
aucat0: 48000Hz, s16le, play 0:1, 20 blocks of 480 frames
aucat0: attached at -7680 + 0/480
app/firefox1.level=127 at 7 -> slot_level:firefox1: added
firefox1: 44100Hz, s16le, play 0:1, 10 blocks of 441 frames
firefox1: attached at -7056 + 0/441
aucat0: attached at -7680 + 0/480
aucat0: 48000Hz, s16le, rec 0:1, 20 blocks of 480 frames

Recording works if the mode of the default sub-device is set to rec rather then play,rec

sndiod -dd -f rsnd/1 -m rec -s default

Bringing the speakerphone in rec-only mode. But, you guess it, no audio playing (kind of walky talky)

The warning about play-only mode is also gone (also kind of expected).

snd0.default: rec=0:1 dup
snd0.0: rec=0:1 dup
default/server.device=0:1 at 1 -> opt_dev:default/0: added
app/firefox0.level=127 at 2 -> slot_level:firefox0: added
0/output0.level=128 at 3 -> hw:0/0: added
0/input0.level=255 at 4 -> hw:0/64: added
0/input0.mute=0 at 5 -> hw:0/96: added
snd0: 16000Hz, s16le, rec 0:0, 16 blocks of 160 frames
firefox0: 48000Hz, s16le, play 0:1, 4 blocks of 480 frames
firefox0 at default: mode not allowed on this sub-device
snd0: device started
firefox0: attached at -7680 + 0/480
app/aucat0.level=127 at 6 -> slot_level:aucat0: added
aucat0: 48000Hz, s16le, play 0:1, 20 blocks of 480 frames
aucat0 at default: mode not allowed on this sub-device
aucat0: attached at -7680 + 0/480
^Cfirefox0: detached at 0 + 0/160
default/server.device=0:1 at 1 -> opt_dev:default/0: removed
snd0: device stopped
snd0: software master level control enabled
0/output.level=127 at 7 -> dev_master:0: added
0/output.level=127 at 7 -> dev_master:0: removed

Changing back to play-only mode, the warning is also gone. Seems that it can be used either in play mode or rec mode but not in play,rec mode (full-duplex).

sndiod -dd -f rsnd/1 -m play -s default

The last test I made was to mix the rec-only mode of the speakerphone and play-only mode with the internal speaker or the AG03 (which kind of worked).

sndiod -dd -f rsnd/1 -m rec -s default -f rsnd/0 -m play -s out
aucat -f snd/0 -o file.wav
aucat -f snd/1.out -i file.wav

Lessons Learned

Shortly after my tests I need to bring the speakerphone back. This closes that case to some degree. Nevertheless I would take the following three key elements from that journey.

Links

[1]: jcs.org openbsd bt audio
[2]: sndiod(8)
[3]: aucat(1)