feat(debug): GUI toggle for DRED verbose logs + macOS mic permission
DRED verbose logs (off by default — keeps logcat clean in normal use): - wzp-codec: DRED_VERBOSE_LOGS atomic flag with dred_verbose_logs() / set_dred_verbose_logs() helpers - opus_enc: gate "DRED enabled" + libopus version logs behind the flag - desktop/src-tauri/engine.rs: gate DredRecvState parse log, reconstruction log, classical PLC log, and DRED-counter fields in the Android recv heartbeat (non-verbose path still logs basic recv stats) - Tauri commands set_dred_verbose_logs / get_dred_verbose_logs - Settings panel gets a "DRED debug logs (verbose, dev only)" checkbox; preference persists in wzp-settings localStorage and is pushed to Rust on save and on app boot macOS mic permission: - Add desktop/src-tauri/Info.plist with NSMicrophoneUsageDescription. Without it, modern macOS silently denies CoreAudio capture for ad-hoc-signed Tauri builds — capture starts but every callback hands you zeros. Symptom: phones could not hear desktop client, desktop could still hear phones (playout has no TCC gate). The Tauri 2 bundler auto-merges this file into WarzonePhone.app's Contents/Info.plist on the next build, so first launch will pop the standard mic prompt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,10 @@
|
||||
<input id="s-agc" type="checkbox" checked />
|
||||
Automatic Gain Control
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input id="s-dred-debug" type="checkbox" />
|
||||
DRED debug logs (verbose, dev only)
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Identity</h3>
|
||||
|
||||
21
desktop/src-tauri/Info.plist
Normal file
21
desktop/src-tauri/Info.plist
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!--
|
||||
Custom Info.plist keys merged into the bundled WarzonePhone.app by
|
||||
tauri-bundler. The base Info.plist (CFBundleIdentifier, version,
|
||||
etc.) is generated from tauri.conf.json — only put *additional*
|
||||
keys here.
|
||||
|
||||
NSMicrophoneUsageDescription is required by macOS TCC for any
|
||||
app that opens an audio input unit. Without this string the OS
|
||||
silently denies CoreAudio capture (input callbacks return zeros)
|
||||
and the app never appears in System Settings → Privacy &
|
||||
Security → Microphone. This was the root cause of the desktop
|
||||
mic regression where phones could not hear the desktop client.
|
||||
-->
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>WarzonePhone needs microphone access to transmit your voice during calls.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -159,7 +159,7 @@ impl DredRecvState {
|
||||
// is steady-state without drowning the log.
|
||||
let should_log = self.parses_with_data == 1
|
||||
|| self.parses_with_data % 100 == 0;
|
||||
if should_log {
|
||||
if should_log && wzp_codec::dred_verbose_logs() {
|
||||
info!(
|
||||
seq,
|
||||
samples_available = available,
|
||||
@@ -225,21 +225,22 @@ impl DredRecvState {
|
||||
match reconstructed {
|
||||
Some(_n) => {
|
||||
self.dred_reconstructions += 1;
|
||||
// Log every DRED reconstruction. These are
|
||||
// rare events on a clean network — when
|
||||
// they fire, we want to know exactly which
|
||||
// gap was filled and how the offset math
|
||||
// played out. Acceptable to be chatty here.
|
||||
info!(
|
||||
missing_seq,
|
||||
anchor_seq = ?self.last_good_seq,
|
||||
offset_samples,
|
||||
offset_ms = offset_samples / 48,
|
||||
samples_available = available,
|
||||
gap_size = gap,
|
||||
total_dred_recoveries = self.dred_reconstructions,
|
||||
"DRED reconstruction fired for missing frame"
|
||||
);
|
||||
// Log every DRED reconstruction (gated behind
|
||||
// the GUI verbose-logs toggle). When enabled,
|
||||
// we want to know exactly which gap was
|
||||
// filled and how the offset math played out.
|
||||
if wzp_codec::dred_verbose_logs() {
|
||||
info!(
|
||||
missing_seq,
|
||||
anchor_seq = ?self.last_good_seq,
|
||||
offset_samples,
|
||||
offset_ms = offset_samples / 48,
|
||||
samples_available = available,
|
||||
gap_size = gap,
|
||||
total_dred_recoveries = self.dred_reconstructions,
|
||||
"DRED reconstruction fired for missing frame"
|
||||
);
|
||||
}
|
||||
emit(out);
|
||||
}
|
||||
None => {
|
||||
@@ -251,8 +252,9 @@ impl DredRecvState {
|
||||
// is whichever check failed in the if
|
||||
// above (offset out of range, no good
|
||||
// state, or reconstruct error).
|
||||
if self.classical_plc_invocations <= 3
|
||||
|| self.classical_plc_invocations % 50 == 0
|
||||
if (self.classical_plc_invocations <= 3
|
||||
|| self.classical_plc_invocations % 50 == 0)
|
||||
&& wzp_codec::dred_verbose_logs()
|
||||
{
|
||||
info!(
|
||||
missing_seq,
|
||||
@@ -695,20 +697,33 @@ impl CallEngine {
|
||||
// Heartbeat every 2s with decode+playout state
|
||||
if heartbeat.elapsed() >= std::time::Duration::from_secs(2) {
|
||||
let fr = recv_fr.load(Ordering::Relaxed);
|
||||
info!(
|
||||
recv_fr = fr,
|
||||
decoded_frames,
|
||||
last_decode_n,
|
||||
last_written,
|
||||
written_samples,
|
||||
decode_errs,
|
||||
codec = ?current_codec,
|
||||
dred_recv = dred_recv.dred_reconstructions,
|
||||
classical_plc = dred_recv.classical_plc_invocations,
|
||||
dred_parses_with_data = dred_recv.parses_with_data,
|
||||
dred_parses_total = dred_recv.parses_total,
|
||||
"recv heartbeat (android)"
|
||||
);
|
||||
if wzp_codec::dred_verbose_logs() {
|
||||
info!(
|
||||
recv_fr = fr,
|
||||
decoded_frames,
|
||||
last_decode_n,
|
||||
last_written,
|
||||
written_samples,
|
||||
decode_errs,
|
||||
codec = ?current_codec,
|
||||
dred_recv = dred_recv.dred_reconstructions,
|
||||
classical_plc = dred_recv.classical_plc_invocations,
|
||||
dred_parses_with_data = dred_recv.parses_with_data,
|
||||
dred_parses_total = dred_recv.parses_total,
|
||||
"recv heartbeat (android)"
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
recv_fr = fr,
|
||||
decoded_frames,
|
||||
last_decode_n,
|
||||
last_written,
|
||||
written_samples,
|
||||
decode_errs,
|
||||
codec = ?current_codec,
|
||||
"recv heartbeat (android)"
|
||||
);
|
||||
}
|
||||
heartbeat = std::time::Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,22 @@ struct PingResult {
|
||||
server_fingerprint: String,
|
||||
}
|
||||
|
||||
/// Toggle DRED verbose logging at runtime (gates the chatty per-frame
|
||||
/// reconstruction + parse logs in opus_enc and engine.rs). Wired to the
|
||||
/// "DRED debug logs" checkbox in the GUI settings panel.
|
||||
#[tauri::command]
|
||||
fn set_dred_verbose_logs(enabled: bool) {
|
||||
wzp_codec::set_dred_verbose_logs(enabled);
|
||||
tracing::info!(enabled, "DRED verbose logs toggled");
|
||||
}
|
||||
|
||||
/// Read the current DRED verbose logging flag (so the GUI can hydrate
|
||||
/// its checkbox on startup without trusting localStorage alone).
|
||||
#[tauri::command]
|
||||
fn get_dred_verbose_logs() -> bool {
|
||||
wzp_codec::dred_verbose_logs()
|
||||
}
|
||||
|
||||
/// Ping a relay to check if it's online, measure RTT, and get server identity.
|
||||
#[tauri::command]
|
||||
async fn ping_relay(relay: String) -> Result<PingResult, String> {
|
||||
@@ -687,6 +703,7 @@ pub fn run() {
|
||||
deregister,
|
||||
set_speakerphone, is_speakerphone_on,
|
||||
get_call_history, get_recent_contacts, clear_call_history,
|
||||
set_dred_verbose_logs, get_dred_verbose_logs,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running WarzonePhone");
|
||||
|
||||
@@ -82,6 +82,7 @@ const settingsBtnCall = document.getElementById("settings-btn-call")!;
|
||||
const sRoom = document.getElementById("s-room") as HTMLInputElement;
|
||||
const sAlias = document.getElementById("s-alias") as HTMLInputElement;
|
||||
const sOsAec = document.getElementById("s-os-aec") as HTMLInputElement;
|
||||
const sDredDebug = document.getElementById("s-dred-debug") as HTMLInputElement;
|
||||
const sAgc = document.getElementById("s-agc") as HTMLInputElement;
|
||||
const sQuality = document.getElementById("s-quality") as HTMLInputElement;
|
||||
const sQualityLabel = document.getElementById("s-quality-label")!;
|
||||
@@ -140,6 +141,10 @@ interface Settings {
|
||||
agc: boolean;
|
||||
quality: string;
|
||||
recentRooms: RecentRoom[];
|
||||
/// When true, the Rust side emits the chatty per-frame DRED parse +
|
||||
/// reconstruction + classical-PLC logs and adds DRED counters to the
|
||||
/// recv heartbeat. Off in normal mode keeps logcat clean.
|
||||
dredDebugLogs: boolean;
|
||||
}
|
||||
|
||||
function loadSettings(): Settings {
|
||||
@@ -152,6 +157,7 @@ function loadSettings(): Settings {
|
||||
],
|
||||
selectedRelay: 0, room: "general", alias: "",
|
||||
osAec: true, agc: true, quality: "auto", recentRooms: [],
|
||||
dredDebugLogs: false,
|
||||
};
|
||||
try {
|
||||
const raw = localStorage.getItem("wzp-settings");
|
||||
@@ -402,6 +408,10 @@ function renderRecentRooms(rooms: RecentRoom[]) {
|
||||
// ── Init ──
|
||||
applySettings();
|
||||
setTimeout(pingAllRelays, 300);
|
||||
// Hydrate the Rust DRED verbose-logs flag from saved settings on boot so
|
||||
// the choice survives app restarts without needing the user to reopen
|
||||
// the settings panel.
|
||||
invoke("set_dred_verbose_logs", { enabled: !!loadSettings().dredDebugLogs }).catch(() => {});
|
||||
|
||||
// Load fingerprint + alias + git hash + render identicon
|
||||
interface AppInfo { git_hash: string; alias: string; fingerprint: string; data_dir: string }
|
||||
@@ -714,6 +724,7 @@ listen("call-event", (event: any) => {
|
||||
function openSettings() {
|
||||
const s = loadSettings();
|
||||
sRoom.value = s.room; sAlias.value = s.alias; sOsAec.checked = s.osAec;
|
||||
sDredDebug.checked = !!s.dredDebugLogs;
|
||||
const qi = qualityToIndex(s.quality || "auto");
|
||||
sQuality.value = String(qi);
|
||||
updateQualityUI(qi);
|
||||
@@ -753,7 +764,11 @@ settingsSave.addEventListener("click", () => {
|
||||
const s = loadSettings();
|
||||
s.room = sRoom.value; s.alias = sAlias.value; s.osAec = sOsAec.checked;
|
||||
s.quality = QUALITY_STEPS[parseInt(sQuality.value)] || "auto";
|
||||
s.dredDebugLogs = sDredDebug.checked;
|
||||
saveSettingsObj(s);
|
||||
// Push the new flag to the Rust side immediately so the next encoded
|
||||
// frame already honors it without waiting for an app restart.
|
||||
invoke("set_dred_verbose_logs", { enabled: s.dredDebugLogs }).catch(() => {});
|
||||
roomInput.value = s.room; aliasInput.value = s.alias; osAecCheckbox.checked = s.osAec;
|
||||
renderRecentRooms(s.recentRooms);
|
||||
closeSettings();
|
||||
|
||||
Reference in New Issue
Block a user