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:
Siavash Sameni
2026-04-11 09:48:32 +04:00
parent 16890576fb
commit 578ff8cff4
7 changed files with 143 additions and 50 deletions

View 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>

View File

@@ -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();
}
}

View File

@@ -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");