mirror of
https://codeberg.org/armin/tcd.git
synced 2026-09-01 14:00:47 +02:00
improve transcode detection at 128/160
This commit is contained in:
parent
7b1a3526b6
commit
ddb31b429d
1 changed files with 99 additions and 24 deletions
125
tcd.c
125
tcd.c
|
|
@ -237,14 +237,43 @@ static int detect_cutoff(const Analyzer *a, double threshold_db,
|
||||||
}
|
}
|
||||||
*out_extended_cutoff = ext_cutoff;
|
*out_extended_cutoff = ext_cutoff;
|
||||||
|
|
||||||
|
/* When the noise floor is above the detection threshold, the cutoff is
|
||||||
|
determined by noise rather than signal. Compute a noise-aware cutoff
|
||||||
|
at (noise floor + 10 dB) for the roughness computation so it operates
|
||||||
|
on signal content rather than noise-dominated bins. */
|
||||||
|
double rough_cutoff = cutoff_hz;
|
||||||
|
if (*out_noise_db > threshold_db) {
|
||||||
|
double adj_db = *out_noise_db + 10.0;
|
||||||
|
double adj_thresh = peak * pow(10.0, adj_db / 20.0);
|
||||||
|
double adj_cutoff = 0;
|
||||||
|
for (int i = n - 1; i >= 0; i--) {
|
||||||
|
if (mag[i] >= adj_thresh) { adj_cutoff = (double)i * sr / a->fft_size; break; }
|
||||||
|
}
|
||||||
|
if (adj_cutoff > 0) rough_cutoff = adj_cutoff;
|
||||||
|
}
|
||||||
|
|
||||||
double roughness = 0.0;
|
double roughness = 0.0;
|
||||||
double cutoff_idx = cutoff_hz * a->fft_size / sr;
|
double cutoff_idx = rough_cutoff * a->fft_size / sr;
|
||||||
int lo = (int)(cutoff_idx * 0.60);
|
int lo = (int)(cutoff_idx * 0.60);
|
||||||
int hi = (int)(cutoff_idx * 0.95);
|
int hi = (int)(cutoff_idx * 0.95);
|
||||||
if (hi >= n) hi = n - 1;
|
if (hi >= n) hi = n - 1;
|
||||||
if (lo < 1) lo = 1;
|
if (lo < 1) lo = 1;
|
||||||
|
|
||||||
if (hi > lo) {
|
/* Check whether the roughness region is noise-dominated. When the noise
|
||||||
|
floor is close to or above the detection threshold the cutoff is
|
||||||
|
determined by noise, and the roughness region may contain mostly noise
|
||||||
|
bins, producing spuriously high roughness values. If fewer than 30 %
|
||||||
|
of bins in the region are more than 6 dB above the noise floor, the
|
||||||
|
roughness is unreliable and we set it to a low value. */
|
||||||
|
double noise_mag = pow(10.0, *out_noise_db / 20.0) * peak;
|
||||||
|
int region_total = hi - lo + 1;
|
||||||
|
int above_noise = 0;
|
||||||
|
for (int i = lo; i <= hi; i++) {
|
||||||
|
if (mag[i] > noise_mag * 2.0) above_noise++;
|
||||||
|
}
|
||||||
|
if ((double)above_noise / region_total < 0.30) {
|
||||||
|
roughness = 0.01;
|
||||||
|
} else if (hi > lo) {
|
||||||
double sum = 0;
|
double sum = 0;
|
||||||
for (int i = lo; i <= hi; i++) sum += mag[i];
|
for (int i = lo; i <= hi; i++) sum += mag[i];
|
||||||
double mean = sum / (hi - lo + 1);
|
double mean = sum / (hi - lo + 1);
|
||||||
|
|
@ -273,10 +302,25 @@ static int detect_cutoff(const Analyzer *a, double threshold_db,
|
||||||
: 0.5;
|
: 0.5;
|
||||||
*out_band_ratio = band_ratio;
|
*out_band_ratio = band_ratio;
|
||||||
|
|
||||||
free(mag);
|
|
||||||
|
|
||||||
double nyquist = sr / 2.0;
|
double nyquist = sr / 2.0;
|
||||||
double cutoff_ratio = cutoff_hz / nyquist;
|
|
||||||
|
/* When the noise floor is above the detection threshold, the cutoff
|
||||||
|
is determined by noise, not signal. Use the noise-aware cutoff
|
||||||
|
(at noise floor + 10 dB) for decision-making to avoid false positives
|
||||||
|
from an inflated cutoff ratio where noise dominates the spectrum. */
|
||||||
|
double decision_cutoff = cutoff_hz;
|
||||||
|
if (*out_noise_db > threshold_db) {
|
||||||
|
double dc_db = *out_noise_db + 10.0;
|
||||||
|
double dc_thresh = peak * pow(10.0, dc_db / 20.0);
|
||||||
|
for (int i = n - 1; i >= 0; i--) {
|
||||||
|
if (mag[i] >= dc_thresh) {
|
||||||
|
decision_cutoff = (double)i * sr / a->fft_size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (decision_cutoff <= 0) decision_cutoff = cutoff_hz;
|
||||||
|
}
|
||||||
|
double cutoff_ratio = decision_cutoff / nyquist;
|
||||||
|
|
||||||
/* Scale detection thresholds by sensitivity (0.0 = least, 0.5 = default, 1.0 = most) */
|
/* Scale detection thresholds by sensitivity (0.0 = least, 0.5 = default, 1.0 = most) */
|
||||||
double bw_factor = 2.0 * (1.0 - sensitivity);
|
double bw_factor = 2.0 * (1.0 - sensitivity);
|
||||||
|
|
@ -290,10 +334,11 @@ static int detect_cutoff(const Analyzer *a, double threshold_db,
|
||||||
|
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|
||||||
if (cutoff_hz <= 0 || cutoff_ratio >= bypass) {
|
if (decision_cutoff <= 0 || cutoff_ratio >= bypass) {
|
||||||
if (roughness > r1) score = 1;
|
if (roughness > r1) score = 1;
|
||||||
else if (roughness > r2 && band_ratio < b1) score = 1;
|
else if (roughness > r2 && band_ratio < b1) score = 1;
|
||||||
else if (roughness > r3 && band_ratio < b2) score = 1;
|
else if (roughness > r3 && band_ratio < b2) score = 1;
|
||||||
|
free(mag);
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,6 +363,7 @@ static int detect_cutoff(const Analyzer *a, double threshold_db,
|
||||||
else if (roughness > r3 && band_ratio < b2) score = 1;
|
else if (roughness > r3 && band_ratio < b2) score = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(mag);
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -693,6 +739,22 @@ static void render_visual_spectrum(
|
||||||
is_native_lossy ? eff_cutoff_ratio : cutoff_ratio,
|
is_native_lossy ? eff_cutoff_ratio : cutoff_ratio,
|
||||||
steepness, roughness, band_ratio, is_native_lossy);
|
steepness, roughness, band_ratio, is_native_lossy);
|
||||||
|
|
||||||
|
/* Compute noise-aware factor ratio for consistent display with detect_cutoff().
|
||||||
|
When the noise floor is above the user's threshold the cutoff is noise-
|
||||||
|
dominated; use (noise floor + 10 dB) for the factor display instead. */
|
||||||
|
double factor_ratio = eff_cutoff_ratio;
|
||||||
|
if (!is_native_lossy && noise_db > threshold_db) {
|
||||||
|
double fc_db = noise_db + 10.0;
|
||||||
|
double fc_thresh = peak * pow(10.0, fc_db / 20.0);
|
||||||
|
int n_bins = fft_size / 2;
|
||||||
|
double fc_cut = cutoff_hz;
|
||||||
|
for (int i = n_bins - 1; i >= 0; i--) {
|
||||||
|
double m = sqrt(a->power[i] / a->count);
|
||||||
|
if (m >= fc_thresh) { fc_cut = (double)i * sample_rate / fft_size; break; }
|
||||||
|
}
|
||||||
|
if (fc_cut > 0) factor_ratio = fc_cut / nyquist;
|
||||||
|
}
|
||||||
|
|
||||||
printf(" " ANSI_BOLD "Decision:" ANSI_RESET " ");
|
printf(" " ANSI_BOLD "Decision:" ANSI_RESET " ");
|
||||||
if (peak_db < -90.0) {
|
if (peak_db < -90.0) {
|
||||||
printf(ANSI_RED "SILENT" ANSI_RESET " (no detectable audio content)");
|
printf(ANSI_RED "SILENT" ANSI_RESET " (no detectable audio content)");
|
||||||
|
|
@ -757,24 +819,25 @@ static void render_visual_spectrum(
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
double fr = factor_ratio;
|
||||||
double max_bw_display;
|
double max_bw_display;
|
||||||
if (cutoff_ratio < 0.50) {
|
if (fr < 0.50) {
|
||||||
max_bw_display = 4000.0;
|
max_bw_display = 4000.0;
|
||||||
} else if (cutoff_ratio < 0.70) {
|
} else if (fr < 0.70) {
|
||||||
max_bw_display = 3000.0;
|
max_bw_display = 3000.0;
|
||||||
} else if (cutoff_ratio < 0.80) {
|
} else if (fr < 0.80) {
|
||||||
max_bw_display = 2000.0;
|
max_bw_display = 2000.0;
|
||||||
} else if (cutoff_ratio < 0.90) {
|
} else if (fr < 0.90) {
|
||||||
max_bw_display = 1200.0;
|
max_bw_display = 1200.0;
|
||||||
} else {
|
} else {
|
||||||
max_bw_display = 500.0;
|
max_bw_display = 500.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int primary_hit = (cutoff_ratio >= 0.99) ? 0 : (steepness < max_bw_display);
|
int primary_hit = (fr >= 0.99) ? 0 : (steepness < max_bw_display);
|
||||||
int secondary_applies = (cutoff_ratio > 0.80);
|
int secondary_applies = (fr > 0.80);
|
||||||
|
|
||||||
if (cutoff_ratio >= 0.99) {
|
if (fr >= 0.99) {
|
||||||
printf(" ① cutoff_ratio=%.3f ≥ 0.99 " ANSI_RED "✗" ANSI_RESET " (full spectrum, no cutoff)", cutoff_ratio);
|
printf(" ① cutoff_ratio=%.3f ≥ 0.99 " ANSI_RED "✗" ANSI_RESET " (full spectrum, no cutoff)", fr);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
} else {
|
} else {
|
||||||
char bw_label[64];
|
char bw_label[64];
|
||||||
|
|
@ -783,11 +846,11 @@ static void render_visual_spectrum(
|
||||||
|
|
||||||
if (primary_hit) {
|
if (primary_hit) {
|
||||||
printf(" ① cutoff_ratio=%.3f, %s " ANSI_GREEN "✓" ANSI_RESET,
|
printf(" ① cutoff_ratio=%.3f, %s " ANSI_GREEN "✓" ANSI_RESET,
|
||||||
cutoff_ratio, bw_label);
|
fr, bw_label);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
} else {
|
} else {
|
||||||
printf(" ① cutoff_ratio=%.3f, transition_bw=%.0fHz ≥ %.0fHz threshold " ANSI_RED "✗" ANSI_RESET,
|
printf(" ① cutoff_ratio=%.3f, transition_bw=%.0fHz ≥ %.0fHz threshold " ANSI_RED "✗" ANSI_RESET,
|
||||||
cutoff_ratio, steepness, max_bw_display);
|
fr, steepness, max_bw_display);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1182,13 +1245,15 @@ static int process_file(const char *filename, const Options *opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
double effective_cutoff = cutoff_hz;
|
double effective_cutoff = cutoff_hz;
|
||||||
if (extended_cutoff > effective_cutoff) effective_cutoff = extended_cutoff;
|
if (extended_cutoff > effective_cutoff && noise_db >= -100.0)
|
||||||
|
effective_cutoff = extended_cutoff;
|
||||||
double cutoff_ratio = effective_cutoff / nyquist;
|
double cutoff_ratio = effective_cutoff / nyquist;
|
||||||
int upscaled = 0;
|
int upscaled = 0;
|
||||||
if (is_native_lossy) {
|
if (is_native_lossy) {
|
||||||
double expected_min = 0.90;
|
double expected_min = 0.90;
|
||||||
if (bitrate > 0 && bitrate < 192000) expected_min = 0.75;
|
if (bitrate > 0 && bitrate < 192000) expected_min = 0.75;
|
||||||
else if (bitrate > 0 && bitrate < 256000) expected_min = 0.85;
|
else if (bitrate > 0 && bitrate < 256000) expected_min = 0.85;
|
||||||
|
if (noise_db < -80.0) expected_min -= 0.05;
|
||||||
if (cutoff_ratio > 0 && cutoff_ratio < expected_min - 0.08) {
|
if (cutoff_ratio > 0 && cutoff_ratio < expected_min - 0.08) {
|
||||||
upscaled = 1;
|
upscaled = 1;
|
||||||
}
|
}
|
||||||
|
|
@ -1223,6 +1288,8 @@ static int process_file(const char *filename, const Options *opts)
|
||||||
printf(ANSI_RESET "\n");
|
printf(ANSI_RESET "\n");
|
||||||
}
|
}
|
||||||
printf(ANSI_BOLD ANSI_CYAN "Format:" ANSI_RESET " %s\n", fmt_name);
|
printf(ANSI_BOLD ANSI_CYAN "Format:" ANSI_RESET " %s\n", fmt_name);
|
||||||
|
if (bitrate > 0)
|
||||||
|
printf(ANSI_BOLD ANSI_CYAN "Bitrate:" ANSI_RESET " %lld kbps\n", (long long)(bitrate / 1000));
|
||||||
printf(ANSI_BOLD ANSI_CYAN "Sample rate:" ANSI_RESET " %d Hz\n", sample_rate);
|
printf(ANSI_BOLD ANSI_CYAN "Sample rate:" ANSI_RESET " %d Hz\n", sample_rate);
|
||||||
printf(ANSI_BOLD ANSI_CYAN "Channels:" ANSI_RESET " %d\n", channels);
|
printf(ANSI_BOLD ANSI_CYAN "Channels:" ANSI_RESET " %d\n", channels);
|
||||||
printf(ANSI_BOLD ANSI_CYAN "Windows:" ANSI_RESET " %d\n", analyzer.count);
|
printf(ANSI_BOLD ANSI_CYAN "Windows:" ANSI_RESET " %d\n", analyzer.count);
|
||||||
|
|
@ -1242,7 +1309,8 @@ static int process_file(const char *filename, const Options *opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
double effective_cutoff = cutoff_hz;
|
double effective_cutoff = cutoff_hz;
|
||||||
if (extended_cutoff > effective_cutoff) effective_cutoff = extended_cutoff;
|
if (extended_cutoff > effective_cutoff && noise_db >= -100.0)
|
||||||
|
effective_cutoff = extended_cutoff;
|
||||||
double cutoff_ratio = effective_cutoff / nyquist;
|
double cutoff_ratio = effective_cutoff / nyquist;
|
||||||
int upscaled = 0;
|
int upscaled = 0;
|
||||||
|
|
||||||
|
|
@ -1250,6 +1318,7 @@ static int process_file(const char *filename, const Options *opts)
|
||||||
double expected_min = 0.90;
|
double expected_min = 0.90;
|
||||||
if (bitrate > 0 && bitrate < 192000) expected_min = 0.75;
|
if (bitrate > 0 && bitrate < 192000) expected_min = 0.75;
|
||||||
else if (bitrate > 0 && bitrate < 256000) expected_min = 0.85;
|
else if (bitrate > 0 && bitrate < 256000) expected_min = 0.85;
|
||||||
|
if (noise_db < -80.0) expected_min -= 0.05;
|
||||||
|
|
||||||
if (cutoff_ratio > 0 && cutoff_ratio < expected_min - 0.08) {
|
if (cutoff_ratio > 0 && cutoff_ratio < expected_min - 0.08) {
|
||||||
upscaled = 1;
|
upscaled = 1;
|
||||||
|
|
@ -1257,16 +1326,22 @@ static int process_file(const char *filename, const Options *opts)
|
||||||
|
|
||||||
if (upscaled) {
|
if (upscaled) {
|
||||||
printf(ANSI_YELLOW "UPSCALED" ANSI_RESET " (re-encoded from a lower-bitrate source)\n");
|
printf(ANSI_YELLOW "UPSCALED" ANSI_RESET " (re-encoded from a lower-bitrate source)\n");
|
||||||
printf(ANSI_BOLD ANSI_CYAN "Verdict Info:" ANSI_RESET " cut-off suggests ");
|
|
||||||
if (cutoff_ratio < 0.70)
|
|
||||||
printf(ANSI_BOLD "<= 64 kbps" ANSI_RESET " source\n");
|
|
||||||
else if (cutoff_ratio < 0.80)
|
|
||||||
printf(ANSI_BOLD "96-128 kbps" ANSI_RESET " source\n");
|
|
||||||
else if (cutoff_ratio < 0.88)
|
|
||||||
printf(ANSI_BOLD "128-192 kbps" ANSI_RESET " source\n");
|
|
||||||
} else {
|
} else {
|
||||||
printf(ANSI_GREEN "NATIVE" ANSI_RESET " (single encode at this bitrate)\n");
|
printf(ANSI_GREEN "NATIVE" ANSI_RESET " (single encode at this bitrate)\n");
|
||||||
}
|
}
|
||||||
|
printf(ANSI_BOLD ANSI_CYAN "Verdict Info:" ANSI_RESET " cutoff=%.1f%% Nyquist, expected≥%.0f%% for %lld kbps",
|
||||||
|
100.0 * cutoff_ratio, 100.0 * expected_min,
|
||||||
|
bitrate > 0 ? (long long)(bitrate / 1000) : 0);
|
||||||
|
if (upscaled) {
|
||||||
|
printf(" → suggests ");
|
||||||
|
if (cutoff_ratio < 0.70)
|
||||||
|
printf(ANSI_BOLD "≤ 64 kbps" ANSI_RESET " source");
|
||||||
|
else if (cutoff_ratio < 0.80)
|
||||||
|
printf(ANSI_BOLD "96–128 kbps" ANSI_RESET " source");
|
||||||
|
else if (cutoff_ratio < 0.88)
|
||||||
|
printf(ANSI_BOLD "128–192 kbps" ANSI_RESET " source");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
} else {
|
} else {
|
||||||
if (is_transcode)
|
if (is_transcode)
|
||||||
printf(ANSI_YELLOW "TRANSCODE" ANSI_RESET " (lossy -> lossless re-encode detected)\n");
|
printf(ANSI_YELLOW "TRANSCODE" ANSI_RESET " (lossy -> lossless re-encode detected)\n");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue