Audio: operator re-measure + format override (host<->hook op channel)
Add a per-stream op channel in AudioRingHeader (op_seq + op_* fields, version 2): the host posts re-measure / override commands, the hook applies them and re-publishes (bumping format_generation), and the host rebuilds its render client live on the change. The Audio panel (under Debug details) gains a "Re-measure rate" button and a rate/channels/bit-depth/format override -- for when detection is wrong or the channels/bit-depth were unrecoverable. Also add a debug-only IPC test harness (-DCOOP_TEST_HARNESS, off by default, absent from the shipped host): a file-based command channel that drives the host's real UI code paths (inject / audio / re-measure / override / screenshot / status) for scripted validation, instead of unreliable synthetic mouse input. Used to validate live: late-attach to coop_tone@44100 -> measured 44100, promoted to hooked; override -> 2ch state, re-measure -> reconverge. Trim the README roadmap to what's left; document the harness + rate_estimator_test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -99,18 +100,32 @@ public:
|
||||
// panel. Empty when on the hooked path or before any fallback decision.
|
||||
[[nodiscard]] std::string fallback_reason() const;
|
||||
|
||||
// Post an operator command (AudioRingOp) to the hook for stream `slot` -- re-measure
|
||||
// the rate or override the format. Thread-safe; queued and applied to the ring on the
|
||||
// audio thread (which owns the ring mappings). For a re-measure the format args are 0.
|
||||
void request_op(unsigned slot, std::uint32_t kind, std::uint32_t rate = 0, std::uint32_t channels = 0,
|
||||
std::uint32_t bits = 0, std::uint32_t format_tag = 0);
|
||||
|
||||
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);
|
||||
// Outcome of a hooked render session.
|
||||
enum class HookedResult
|
||||
{
|
||||
Stopped, // clean stop (mirror stopping) -> done
|
||||
Failed, // setup failed (format not renderable) -> caller falls back to loopback
|
||||
Reinit, // the hook re-published the format (re-measure/override) -> re-read and retry
|
||||
};
|
||||
// Runs the hooked (no-echo) render path until stop, a setup failure, or a format change
|
||||
// (re-measure/override). `rings[0]` is the primary stream; additional non-null rings are
|
||||
// mixed in.
|
||||
HookedResult run_hooked(AudioRingHeader* const* rings);
|
||||
// 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);
|
||||
void drain_ops(); // audio thread: post queued operator ops to the session rings
|
||||
bool stop_requested() const;
|
||||
void set_status(std::string s);
|
||||
void set_fallback_reason(std::string s);
|
||||
@@ -120,6 +135,17 @@ private:
|
||||
DWORD pid_ = 0;
|
||||
|
||||
SharedMemory audio_ring_shm_[kMaxAudioStreams]; // per-stream rings (coop_audio_<pid>[_<i>])
|
||||
AudioRingHeader* session_rings_[kMaxAudioStreams] = {}; // set on the audio thread for the session
|
||||
|
||||
// Operator ops queued by request_op (any thread) and applied to the rings on the
|
||||
// audio thread (which owns the mappings). Guarded by ops_mutex_.
|
||||
struct PendingOp
|
||||
{
|
||||
unsigned slot;
|
||||
std::uint32_t kind, rate, channels, bits, format_tag;
|
||||
};
|
||||
std::mutex ops_mutex_;
|
||||
std::vector<PendingOp> pending_ops_;
|
||||
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<Source> source_{Source::None};
|
||||
|
||||
Reference in New Issue
Block a user