diff --git a/crates/wzp-codec/src/lib.rs b/crates/wzp-codec/src/lib.rs
index 30cf1e7..f923170 100644
--- a/crates/wzp-codec/src/lib.rs
+++ b/crates/wzp-codec/src/lib.rs
@@ -28,6 +28,26 @@ pub use denoise::NoiseSupressor;
pub use silence::{ComfortNoise, SilenceDetector};
pub use wzp_proto::{AudioDecoder, AudioEncoder, CodecId, QualityProfile};
+use std::sync::atomic::{AtomicBool, Ordering};
+
+/// Global verbose-logging flag for DRED. Off by default — when enabled
+/// (via the GUI debug toggle wired through Tauri), the encoder logs its
+/// DRED config + libopus version, and the recv path logs every DRED
+/// reconstruction, classical PLC fill, and parse heartbeat. Off in
+/// "normal" mode keeps logcat clean.
+static DRED_VERBOSE_LOGS: AtomicBool = AtomicBool::new(false);
+
+/// Returns whether DRED verbose logging is currently enabled.
+#[inline]
+pub fn dred_verbose_logs() -> bool {
+ DRED_VERBOSE_LOGS.load(Ordering::Relaxed)
+}
+
+/// Enable/disable DRED verbose logging at runtime.
+pub fn set_dred_verbose_logs(enabled: bool) {
+ DRED_VERBOSE_LOGS.store(enabled, Ordering::Relaxed);
+}
+
/// Create an adaptive encoder starting at the given quality profile.
///
/// The returned encoder accepts 48 kHz mono PCM regardless of the active
diff --git a/crates/wzp-codec/src/opus_enc.rs b/crates/wzp-codec/src/opus_enc.rs
index 43bf756..b5e156a 100644
--- a/crates/wzp-codec/src/opus_enc.rs
+++ b/crates/wzp-codec/src/opus_enc.rs
@@ -186,25 +186,26 @@ impl OpusEncoder {
.set_packet_loss(DRED_LOSS_FLOOR_PCT)
.map_err(|e| CodecError::EncodeFailed(format!("set packet loss floor: {e:?}")))?;
- // Bumped from debug! to info! so the DRED config is visible in
- // logcat without enabling debug-level filtering. Each call's
- // first OpusEncoder construction will log this; subsequent
- // profile switches log it again with the new tier.
- info!(
- codec = ?codec,
- dred_frames,
- dred_ms = dred_frames as u32 * 10,
- loss_floor_pct = DRED_LOSS_FLOOR_PCT,
- "opus encoder: DRED enabled"
- );
+ // Both of these are gated behind the GUI debug toggle so logcat
+ // stays clean in normal mode. Flip "DRED verbose logs" in the
+ // settings panel to see the per-encoder config + libopus version.
+ if crate::dred_verbose_logs() {
+ info!(
+ codec = ?codec,
+ dred_frames,
+ dred_ms = dred_frames as u32 * 10,
+ loss_floor_pct = DRED_LOSS_FLOOR_PCT,
+ "opus encoder: DRED enabled"
+ );
- // One-shot logging of the linked libopus version so we can
- // confirm at a glance that opusic-c (libopus 1.5.2) is loaded.
- // Pre-Phase-0 audiopus shipped libopus 1.3 which has no DRED;
- // if this log says "libopus 1.3" something is very wrong.
- LIBOPUS_VERSION_LOGGED.get_or_init(|| {
- info!(libopus_version = %opusic_c::version(), "linked libopus version");
- });
+ // One-shot logging of the linked libopus version so we can
+ // confirm at a glance that opusic-c (libopus 1.5.2) is loaded.
+ // Pre-Phase-0 audiopus shipped libopus 1.3 which has no DRED;
+ // if this log says "libopus 1.3" something is very wrong.
+ LIBOPUS_VERSION_LOGGED.get_or_init(|| {
+ info!(libopus_version = %opusic_c::version(), "linked libopus version");
+ });
+ }
Ok(())
}
diff --git a/desktop/index.html b/desktop/index.html
index 9f31ac9..2770fc2 100644
--- a/desktop/index.html
+++ b/desktop/index.html
@@ -169,6 +169,10 @@
Automatic Gain Control
+
Identity
diff --git a/desktop/src-tauri/Info.plist b/desktop/src-tauri/Info.plist
new file mode 100644
index 0000000..15e1cfd
--- /dev/null
+++ b/desktop/src-tauri/Info.plist
@@ -0,0 +1,21 @@
+
+
+
+
+
+ NSMicrophoneUsageDescription
+ WarzonePhone needs microphone access to transmit your voice during calls.
+
+
diff --git a/desktop/src-tauri/src/engine.rs b/desktop/src-tauri/src/engine.rs
index 884095b..63cdd60 100644
--- a/desktop/src-tauri/src/engine.rs
+++ b/desktop/src-tauri/src/engine.rs
@@ -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();
}
}
diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs
index 44205ca..985ef5a 100644
--- a/desktop/src-tauri/src/lib.rs
+++ b/desktop/src-tauri/src/lib.rs
@@ -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 {
@@ -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");
diff --git a/desktop/src/main.ts b/desktop/src/main.ts
index 7cd6640..df31422 100644
--- a/desktop/src/main.ts
+++ b/desktop/src/main.ts
@@ -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();