Audio: keep rings live, auto-promote loopback->hooked, show the reason

The host used to wait a fixed 1 s for the hook to publish a format and, on
timeout, fall to loopback permanently with no explanation -- which the now
slower (consensus) measurement made common. Restructure the audio thread to
keep the rings live the whole session and alternate: prefer hooked, and while
it isn't ready run loopback (echo) so guests still hear audio, watching the
ring to promote to the no-echo hooked path the instant the hook publishes a
format. Surface the concrete reason loopback is active (no format yet / not
renderable / no ring) in the Audio panel, in amber.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 01:16:46 +02:00
parent cb6749b511
commit c24bfdd64f
3 changed files with 115 additions and 16 deletions

View File

@@ -95,16 +95,25 @@ public:
[[nodiscard]] std::string status() const;
// Why the loopback (echo) path is active instead of the hooked one, for the Audio
// panel. Empty when on the hooked path or before any fallback decision.
[[nodiscard]] std::string fallback_reason() const;
private:
void thread_main(DWORD pid);
// Returns true if it owned the session to a clean stop; false if setup failed
// and the caller should fall back to the loopback path. `rings[0]` is the primary
// stream; additional non-null rings are mixed in.
bool run_hooked(AudioRingHeader* const* rings);
void run_loopback(DWORD pid);
// Loopback (echo) capture. If `promote_ring` is non-null, returns true the moment
// that ring's format becomes ready (the hook caught up -> caller promotes to hooked);
// returns false when stopped. With a null ring it only returns false (on stop).
bool run_loopback(DWORD pid, AudioRingHeader* promote_ring);
bool wait_for_format(AudioRingHeader* ring, DWORD timeout_ms);
static void enable_capture(AudioRingHeader* const* rings, bool on);
bool stop_requested() const;
void set_status(std::string s);
void set_fallback_reason(std::string s);
std::thread thread_;
HANDLE stop_event_ = nullptr;
@@ -120,6 +129,7 @@ private:
mutable std::mutex status_mutex_;
std::string status_;
std::string fallback_reason_; // why loopback is active (shown in the panel); guarded by status_mutex_
};
} // namespace coop