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, 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(8) and aucat(1). 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.