This repository has no description

README.md

hisense-bt-fix #

Everything needed to make a Hisense AX3100G soundbar (and likely similar Hisense/ErP-compliant soundbars) work reliably as an A2DP Bluetooth speaker on a fresh Linux install — developed on an Orange Pi Zero 3W (aic8800 UART BT, BlueZ 5.82, PulseAudio 17), but nothing here is board-specific.

The soundbar has two independent firmware quirks. macOS happens to avoid both, so it "just works" from a Mac; stock Linux hits both.

Quirk 1 — silent audio: oversized A2DP packets are dropped #

The soundbar's L2CAP Configure Request carries no MTU, which means the default of 672 bytes. The Linux kernel nevertheless negotiates an outgoing MTU of 1021, so PulseAudio happily sends ~965-byte media packets. The soundbar silently discards them: the link stays up, the stream shows "active", every packet is ACKed — and no sound comes out.

Fix: btmtu.c — a tiny LD_PRELOAD shim on bluetoothd that caps the MTU reported by getsockopt() at 672 (override with env BT_MTU_CAP). PulseAudio sizes its packets from bluetoothd's Acquire() reply, so capping it there fixes every audio client at once. (PulseAudio's autodetect_mtu option does not help — it only affects HFP/SCO.)

Installed as /usr/local/lib/btmtu.so + systemd drop-in bluetooth.service.d/btmtu.conf. Note: bluetooth.service runs with ProtectHome=true, so the .so must live outside /home.

Quirk 2 — random shutdown mid-music: AVRCP-inactivity auto-standby #

The soundbar's mandatory ~15-minute ErP auto-power-off is an inactivity timer that ignores the audio entirely. Empirically (measured on repeated sessions, HCI disconnect reason 3 = remote hung up, LED dark):

  • real audio at −12 dBFS peaks does NOT reset it — it powers off mid-music, exactly 15m03s after connecting;
  • inaudible pilot tones (19 kHz / 25 Hz at ~−20 dBFS) do NOT reset it;
  • a static AVRCP PlaybackStatus: Playing does NOT reliably reset it;
  • what a Mac/phone produces naturally — an AVRCP player whose position advances and tracks change, plus occasional volume commands — DOES.

Fix (two cooperating services):

  • bin/mpris-keepalive.py — fake MPRIS player (org.mpris.MediaPlayer2.btkeepalive) reporting Playing with a position that advances every 15 s and a track change every 4 min. mpris-proxy (ships with BlueZ, must run in the user session) forwards it to bluetoothd, whose AVRCP target sends the resulting notifications to the soundbar.
  • bin/bt-volnudge — every 4 min sends AVRCP passthrough VolumeDown+VolumeUp (net zero, like tapping the remote). This soundbar has no AVRCP absolute-volume support, so passthrough keys are the only volume channel.

With both active (plus the optional pilot tones below) a session survived 75+ minutes; without them every session died in 15–53 min.

Optional: bin/bt-pilot keeps inaudible 19 kHz + 25 Hz sine tones (PulseAudio module-sine) mixed into the sink. Level-based standby was disproven for this model, so these are likely unnecessary — kept as an option for soundbars whose detector is level-based. Disable with systemctl --user disable --now bt-pilot.

Caveat: AVRCP only helps when its control channel actually connects. This soundbar opens AVCTP at the same instant as BlueZ and the collision sometimes kills the channel (visible as MediaControl1.Connected: false). Sessions without AVRCP may still hit the 15-min timer. Watch it with bin/bt-watch (logs avrcp=true/false every 10 s).

Contents #

file purpose
btmtu.c LD_PRELOAD MTU-cap shim source (quirk 1)
systemd/btmtu.conf bluetooth.service drop-in loading the shim
bin/mpris-keepalive.py live fake MPRIS player: Playing + advancing position + track changes (quirk 2)
systemd/mpris-keepalive.service user unit for the keepalive
bin/bt-volnudge + systemd/bt-volnudge.service 4-min AVRCP passthrough volume nudge (quirk 2)
bin/bt-pilot + systemd/bt-pilot.service optional inaudible pilot tones
bin/bt-watch + systemd/bt-watch.service diagnostic logger: connect/disconnect events, AVRCP state, link quality, audio level → ~/.local/share/bt-watch.log
bin/bt-test play left/right/both channel test tones on the soundbar
install.sh builds + installs all of the above

Install on a fresh system #

Requirements: bluez (with mpris-proxy), pulseaudio (or PipeWire's libspa-bluez* — untested), gcc, python3-dbus, python3-gi.

./install.sh AC:B1:EE:D7:6F:1E     # your soundbar's MAC (bluetoothctl devices)

The script uses sudo for the system half (shim + bluetooth restart) and installs the user half (keepalive, watcher, mpris-proxy) as your user.

Then pair once:

bluetoothctl
> scan on          # power the soundbar on, wait for it to appear
> pair   AC:B1:EE:D7:6F:1E
> trust  AC:B1:EE:D7:6F:1E
> connect AC:B1:EE:D7:6F:1E

Verify #

journalctl -u bluetooth | grep btmtu      # "capping omtu 1021->672" on connect
bt-test both                              # left/right test tones are audible
busctl --system get-property org.bluez \
  /org/bluez/hci0/dev_<MAC_with_underscores> org.bluez.MediaControl1 Connected
tail -f ~/.local/share/bt-watch.log       # conn=yes avrcp=true sink=RUNNING ...

A session should now survive well past the 15-minute mark while playing.

Debugging a drop #

Enable BlueZ debug to capture the HCI disconnect reason (1 = link loss, 2 = we hung up, 3 = the soundbar hung up):

sudo sh -c 'printf "[Service]\nExecStart=\nExecStart=/usr/libexec/bluetooth/bluetoothd -d\n" \
  > /etc/systemd/system/bluetooth.service.d/debug.conf && systemctl daemon-reload && systemctl restart bluetooth'
journalctl -u bluetooth -f | grep -iE "reason|avctp|set_status"

Harmless noise you can ignore: AVRCP: failed to init uinput (the uinput kernel module is absent on many vendor kernels; it only affects remote-control key forwarding, not audio or AVRCP status).