Voice: keep a silent output open on USB speakerphones so the mic streams - #70
Merged
Merged
Conversation
Measured on vadelma 26 Sep 2026: the Jabra Speak2 40 (0b0e:ae6b) fails every arecord with "read error: Input/output error" unless a playback stream is open on the same card, so listen() reopened the mic every ~3 s and heard nothing. While the mic is open on a USB device, a silent aplay (S16_LE 16 kHz 2 ch from /dev/zero) now holds the card's output open. It starts in _open_mic and stops in the new _close_mic, used on every close path (reopen, before _speak, shutdown); also stopped before the wedged-device pkill. BT HFP and default-device paths are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced Sep 26, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug (measured on vadelma, Pi 5, 26 Sep 2026 11:42-11:45Z)
/proc/asound/card0/stream0: Playback S16_LE 2 ch at 48000/44100/32000/16000/8000; Capture S16_LE 1 ch at 16000 only.arecord -D plughw:0,0 -f S16_LE -r 16000 -c 1 -d 2 x.wavfails every time withpcm_read:2272: read error: Input/output error(also onhw:0,0), listener paused, no kernel messages.aplay -q -D plughw:0,0 -f S16_LE -r 16000 -c 2 /dev/zero &holds the card's output open._open_mic()opened only arecord, every read came back empty, andlisten()reopened the mic forever: the journal showsmic: USB audio plughw:0,0every ~3 s and nothing is ever heard.The fix
While the mic is open on a USB audio device, a silent
aplay -D <same plughw:N,M> -f S16_LE -r 16000 -c 2 -t raw /dev/zeroholds that card's output open._open_mic()starts it (USB branch only) right before arecord, with a 0.3 s settle so arecord does not race the output coming up and trip the "died within 2 s = wedged" branch. A new_close_mic(proc)terminates/reaps the arecord (same terminate, wait, kill fallback as before) and then the keepalive. All three close paths use it: the reopen path, before_speak, and thefinallyon shutdown._open_mic()also stops any leftover keepalive first, so there are never two._speak, not dmix._speakplays straight toplughwon the same card and a hw PCM is exclusive. A dmix would need an asoundrc on the Pi, which is deploy-side config this repo does not own. The mic is already closed for every reply, so the keepalive goes down with it and comes back when the mic reopens. Replies still play on the Jabra._stop_keepalive()terminates, waits, and falls back to kill, the same way arecord is handled. The wedged-device branch stops our keepalive by handle before itspkill -9 -x arecord. There is deliberately no blanketpkill aplay, because that would also kill reply playback. On a service restart, systemd's default control-group KillMode takes the child aplay down too.CARWATCH_MIC=btwith USB attached.mic: keepalive aplay exited (N), and capture goes ahead exactly as before.plughw:N,Mrather than_usb_audio_device("playback"), because that one picks the first matching card, which with two USB devices could be a different card from the mic.wait()s on the dead arecord (through_close_mic), so it gets reaped instead of left as a zombie. For a process that has already exited, this returns immediately.Tests (run on the MacBook, Python 3.14.3, subprocess mocked)
New file
tests/test_usb_mic_keepalive.pyhas 12 tests:_speakruns, and both come back afterCARWATCH_MIC=btwith USB present, and the default device all get no aplay, both in_open_micand through thelisten()looppython3 -m unittest tests.test_usb_mic_keepalive_stop_keepalivedrops the handle without terminating_close_micno longer stops the keepalive_open_mic's own stoppython3 -m unittest discover -s testsDeploy + verify on vadelma: NOT DONE (the owner deploys on his word)
cd ~/CarWatch && git pullonce this is merged, or check out this branch.sudo systemctl restart carwatch-listen.servicejournalctl -u carwatch-listen -f:mic: USB audio plughw:0,0should appear once and then stop repeating. The ~3 s reopen storm must be gone, and there should be nokeepalive aplay exitedline for the Jabra.heard: ...) in the room.mic: USB audioline, no storm).pgrep -a aplaywhile idle should show exactly one/dev/zeroaplay, never more, even after several exchanges.🤖 Generated with Claude Code