fix: say when a capture device is streaming nothing
Second empty recording from the same station, and this time NEITHER source had
delivered a sample — so the fix for a silent second source could not apply, and
the log still said only "recording was empty" at the end of the QSO.
Two blind spots, both now closed:
- captureStream's error was discarded. A device that cannot be opened — renamed,
unplugged, held by another program — looked exactly like one that is merely
quiet.
- A WASAPI device can open cleanly and then produce nothing at all. A DAX
channel with no stream behind it does precisely that, and it is
indistinguishable from silence until the file turns out to be empty.
A watchdog now reports both, three seconds in, and the first goes to the OPERATOR
as a toast rather than only to the log: they are mid-QSO, and by the time they
find out at save time the audio is gone for good.
This commit is contained in:
@@ -15,6 +15,14 @@ import (
|
||||
// Defaults to a no-op so the package is usable without wiring.
|
||||
var LogSink = func(string, ...any) {}
|
||||
|
||||
// AlertSink receives the few audio problems an operator must see WHILE they are
|
||||
// operating, not afterwards in a log file — a capture device that opens but
|
||||
// never streams being the one that matters: the recording is silently empty and
|
||||
// nothing says so until the QSO is logged and gone.
|
||||
//
|
||||
// Set to a toast emitter at startup; a no-op keeps the package standalone.
|
||||
var AlertSink = func(string, ...any) {}
|
||||
|
||||
// recoverGoroutine turns a panic in a long-running audio goroutine into a logged
|
||||
// event with a stack trace instead of a silent process-killing crash. (It can't
|
||||
// catch a hard Windows access violation from the WASAPI layer — those are fatal
|
||||
@@ -136,29 +144,63 @@ func (r *Recorder) Start(fromDev, micDev string, prerollSec int) error {
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
defer recoverGoroutine("recorder capture (radio)")
|
||||
_ = captureStream(fromDev, stop, func(chunk []byte) {
|
||||
// The error was discarded here. A device that cannot be opened — renamed,
|
||||
// unplugged, held by another program — then looked exactly like a device
|
||||
// that is merely quiet, and the recording came out empty with nothing in
|
||||
// the log to say why.
|
||||
if err := captureStream(fromDev, stop, func(chunk []byte) {
|
||||
s := bytesToInt16(chunk)
|
||||
r.srcMu.Lock()
|
||||
r.bufA = append(r.bufA, s...)
|
||||
r.lastA = time.Now()
|
||||
r.srcMu.Unlock()
|
||||
})
|
||||
}); err != nil {
|
||||
LogSink("recorder: capture from %q failed: %v", fromDev, err)
|
||||
}
|
||||
}()
|
||||
if twoSrc {
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
defer recoverGoroutine("recorder capture (mic)")
|
||||
_ = captureStream(micDev, stop, func(chunk []byte) {
|
||||
if err := captureStream(micDev, stop, func(chunk []byte) {
|
||||
s := bytesToInt16(chunk)
|
||||
r.srcMu.Lock()
|
||||
r.bufB = append(r.bufB, s...)
|
||||
r.lastB = time.Now()
|
||||
r.srcMu.Unlock()
|
||||
})
|
||||
}); err != nil {
|
||||
LogSink("recorder: capture from %q failed: %v", micDev, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Watchdog. A WASAPI device can open cleanly and then produce nothing at
|
||||
// all — a DAX channel with no stream behind it does exactly that, and it is
|
||||
// indistinguishable from silence until the recording turns out to be empty
|
||||
// at the end of a QSO. Say it once, three seconds in, while there is still
|
||||
// time to fix the setup.
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
defer r.wg.Done()
|
||||
defer recoverGoroutine("recorder watchdog")
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-time.After(3 * time.Second):
|
||||
}
|
||||
r.srcMu.Lock()
|
||||
aQuiet, bQuiet := r.lastA.IsZero(), r.lastB.IsZero()
|
||||
r.srcMu.Unlock()
|
||||
if aQuiet {
|
||||
LogSink("recorder: no audio at all from %q after 3 s — the device opened but nothing is streaming", fromDev)
|
||||
AlertSink("No audio from %s — nothing is being recorded", fromDev)
|
||||
}
|
||||
if twoSrc && bQuiet {
|
||||
LogSink("recorder: no audio at all from %q after 3 s — the device opened but nothing is streaming", micDev)
|
||||
}
|
||||
}()
|
||||
|
||||
// Mixer goroutine.
|
||||
r.wg.Add(1)
|
||||
go func() {
|
||||
|
||||
Reference in New Issue
Block a user