Files
bal-electrum-plugin/AUDIO_MODEM_DEBIAN.md

6.8 KiB
Raw Blame History

Audio MODEM on Debian — setup & troubleshooting

How to make the optional audio channel of BAL (and Electrum's own audio_modem plugin) work on Debian/Ubuntu. The channel lets you send a will to another device as acoustic OFDM tones instead of scanning QR codes.

Recommended reading before starting: CHANGELOG.md entry 56 (Audio-environment notes) and HANDOFF.md (Dev-box audio prerequisites).


1. What you need (three independent pieces)

Piece Provides Where it comes from
amodem (Python) OFDM modulation/demodulation pip install amodem (any venv)
libportaudio.so sound I/O backend used by amodem.audio Debian package libportaudio2 (+ dev symlink, see §2)
Electrum audio_modem the plugin whose _send/_recv BAL reuses built into Electrum

BAL shows the audio buttons only when the plugin is enabled (Tools → Plugins → Audio Modem) and amodem is importable.

On a headless/CI box there is no speaker/mic, but the channel can still be verified with the sink-monitor loopback in §4.


2. The two line fixes (this is the part everyone forgets)

Debian ships a versioned libportaudio.so.2 but not the unversioned libportaudio.so that old amodem code uses, and amodem uses NumPy APIs removed in NumPy 2.x. Both fail silently (the plugin's _send runs the load inside a WaitingDialog thread without an on_error handler).

Install the dev package (creates the unversioned symlink), or create it by hand:

sudo apt install libportaudio2 libportaudio-dev   # preferred
# or, without the package:
sudo ln -s /usr/lib/x86_64-linux-gnu/libportaudio.so.2 \
           /usr/lib/x86_64-linux-gnu/libportaudio.so

Verify:

source "$BAL_HOME/electrum/env/bin/activate"
python3 -c "import amodem.audio; print(amodem.audio.Interface(config=None).load('libportaudio.so').call('GetVersionText'))"
# b'PortAudio V19...'  <-- success

No-sudo alternative (fine for one-shot tests): point LD_LIBRARY_PATH at a directory containing a libportaudio.so symlink to the .so.2:

mkdir -p /tmp/portaudio_stub
ln -s /usr/lib/x86_64-linux-gnu/libportaudio.so.2 /tmp/portaudio_stub/libportaudio.so
export LD_LIBRARY_PATH=/tmp/portaudio_stub:$LD_LIBRARY_PATH

2b. amodem vs NumPy 2.x (tostring removed)

amodem 1.16.0 calls numpy.ndarray.tostring(), removed in NumPy 2.x (≥ 2.4.6 dies with AttributeError on the first sample write, so no carrier is ever emitted). Either pin NumPy < 2, or patch the single line in the installed package:

source "$BAL_HOME/electrum/env/bin/activate"
python3 -m pip install "numpy<2"   # option A (downgrade)
# option B (patch; path depends on your site-packages):
sed -i "s/sym.astype('int16').tostring()/sym.astype('int16').tobytes()/" \
  "$BAL_HOME/electrum/env/lib/python3.11/site-packages/amodem/common.py"

This must be done on every machine that receives/sends audio (both ends of the channel use the same code), and again after reinstalling/upgrading amodem.


3. Environment checklist (dev box, already applied)

These were applied on the current dev box and do NOT need to be re-done:

  • amodem installed in the runtime venv (1.16.0).
  • amodem/common.py patched tostring()tobytes().
  • System symlink or LD_LIBRARY_PATH stub for libportaudio.so.
  • PulseAudio running; default sink ALC236 Analog, default source DMIC.

Check them in one command:

source "$BAL_HOME/electrum/env/bin/activate"
python3 - <<'EOF'
import amodem, ctypes, numpy, zlib
print("amodem", amodem.__version__)
print("numpy", numpy.__version__, "(2.x needs the tobytes patch)")
import amodem.audio
amodem.audio.Interface(config=None).load("libportaudio.so")
print("libportaudio.so loaded OK (symlink or LD_LIBRARY_PATH in place)")
EOF

4. Verifying the channel (no speakers/mic needed)

Full send → sink → sink-monitor → recv round-trip on one machine:

# 1) route capture at the loop and remember the original source
MON="$(pactl get-default-sink).monitor"; ORIG=$(pactl get-default-source)
pactl set-default-source "$MON"

# 2) run the round-trip (uses zlib-compressed payload like the plugin)
source "$BAL_HOME/electrum/env/bin/activate"
timeout 90 python3 /tmp/opencode/bal_audio_loopback.py
#   expected: bitrate 1.0 kbps ... send done ... RECV OK

# 3) restore the original source
pactl set-default-source "$ORIG"

Any payload you like: python3 /tmp/opencode/bal_audio_loopback.py "BALQR|1|1|0|hi".

With real speakers + mic instead, skip the pactl swapping, put the devices close, keep volumes high, and run the same script.


5. Testing through the real GUI

  1. Tools → (Plugins) → Audio Modem → enable it. If asked for settings, pick a bitrate: default slowest() is ~1.01.2 kbps (a ~2 KB will takes ~1520 s of audio); higher bitrates are faster but less robust.
  2. Wallet A → BAL will list → Export → QR Codes → Audio… (the audio transport sends the raw newline-joined tx list, no BAL framing).
  3. Wallet B → will list → Import via QR → Audio… → wait for "Waiting for audio (... kbps)…", a loading cursor while demodulating, then the decoded slots appear → review/sign wizard opens.
  4. One machine only: apply the §4 monitor trick in the shell where Electrum runs (export plays to the sink; import records from the sink monitor).

6. Troubleshooting

Symptom Cause Fix
No sound at all, no error anywhere in the log libportaudio.so not loadable (silent) §2a symlink or LD_LIBRARY_PATH stub
Sound played, "Timeout waiting for carrier" on the receive end Capture routed to the wrong device / mic muted / no speakers §4 monitor trick; pactl source check; raise volume; move devices closer
"Decoding failed" after carrier, no payload Send side died with numpy tostring → nothing modulated §2b patch or numpy<2 on BOTH machines
Buttons "Audio…" missing in BAL dialogs audio_modem disabled in Plugins, or amodem not importable in the running venv Enable plugin; pip install amodem
Audio too long / too slow 1 kbps default Raise bitrate in Audio Modem settings dialog

7. No-sudo quick reference (all commands)

python3 -m pip install amodem
mkdir -p /tmp/portaudio_stub
ln -s /usr/lib/x86_64-linux-gnu/libportaudio.so.2 /tmp/portaudio_stub/libportaudio.so
export LD_LIBRARY_PATH=/tmp/portaudio_stub:$LD_LIBRARY_PATH
# numpy >= 2 (one of):
pip install "numpy<2"   # or patch amodem/common.py tobytes