Files
Sylpheed/tools/port/check-capture
Sylpheed port agent ff8d608b1e port: check-capture passed a file that was 36% holes -- it now catches starvation
The Decoder diagnosed take 2 as a STARVED capture and I verified it here rather
than take it on trust: 35.6% of frames silent on all six channels, 10482
alternating runs, median burst 13.5 ms and gap 3.9 ms, a 17.4 ms period at 57 Hz.
Their untruncated original reads 39.3% and 10595 runs; the difference is exactly
the truncation and every other number agrees.

So my rebuilt correlator was working correctly on a file that could not carry the
signal. The alarming reading it produced -- that the game may not play the .wmv's
WMA track, so ADV.ogv's audio has been wrong since P4 -- is NOT SUPPORTED by this
capture and is not refuted either. Withdrawn as a concern arising from evidence,
with nothing changed in either direction. It was the most expensive-to-act-on
hypothesis in the port and it came from a file that could not speak to it.

THE REAL DEFECT WAS MINE: `check-capture` tested only for duplicated channels, so
it cleared a recording that was 36% holes. A provenance check that passes the
artefact it was built in response to is not a check.

It now measures starvation, and TWO THRESHOLDS I INVENTED WERE BOTH WRONG:

  counting exact-zero frames -- real audio crosses zero constantly, so a clean
    voice track scored 5947 "gaps" of median 0.0 ms and was called starved. A gap
    is a RUN, not a sample; only runs over 1 ms count.
  gap count and median length -- a genuine music bed shows 454 gaps at a median
    of 1.4 ms, because quiet 16-bit passages really are zero for milliseconds.

What separates them is the RATE: 32.9 gaps/s starved, 3.3 for a real bed, 0.03
for a voice track that is 53% pauses. Bar at 20/s, derived from those controls
rather than chosen and then justified. Controlled both directions: real stereo
bed PASS, six distinct tones PASS, starved capture FAIL. It also reports a `data`
chunk declaring 0 bytes -- what a file copied mid-write looks like, which is what
happened.

VOICE CHANNEL ROLES ARE NOT OBTAINABLE THIS SESSION. Both routes closed: the
monitor sink is starved by construction, and the internal tap at
SDLAudioDriver::SubmitFrame needs a Canary rebuild the Decoder has costed at a
whole session. That is the human's call, not an agent's. The port keeps authoring
with the known recorded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-29 16:25:17 +00:00

174 lines
8.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Provenance check for a multichannel capture, BEFORE anybody analyses it.
#
# tools/port/check-capture /path/to/capture.wav
#
# WHY THIS EXISTS. A 6-channel capture of the game's own output was analysed at
# length -- three controls, a drift test, a written-up negative -- and the file
# was corrupt. PulseAudio was remapping between two mismatched channel maps, and
# a 6-channel remap SILENTLY DROPS AND DUPLICATES: right duration, right channel
# count, plausible per-channel levels, no error anywhere. Two of the six channels
# were byte-identical copies of two others and two source channels were simply
# gone.
#
# The Decoder proved it with a control that needs no emulator and no disc: six
# channels each carrying a different tone through the same sink and the same
# `parec` invocation. Channels came back 400 / 3200 / 200 / 800 / 800 / 200 for
# an input of 400 / 800 / 200 / 1600 / 3200 / 6400 -- see
# `docs/re/audio-capture-channel-map-trap.md`. Setting the sink's `channel_map`
# to the guest's own and passing the same map to `parec` returns all six.
#
# THE DETECTABLE SIGNATURE IS AN EXACT DUPLICATE PAIR. Two channels of a real
# surround mix are never byte-identical over 70 s. Levels are not enough to catch
# it -- the corrupt file's per-channel peaks looked entirely reasonable, and it
# was only equal peak AND equal RMS to six decimals that prompted a hash.
#
# This is a NECESSARY check, not a sufficient one: passing it means the capture
# has no duplicated channels, not that it recorded the right thing.
set -euo pipefail
f="${1:?usage: check-capture FILE.wav}"
# Queried one field at a time. A combined `-show_entries` prints two values on
# ONE comma-separated line, and `read -r ch rate dur` then puts "48000,6" in
# `$ch` -- which every later arithmetic test rejects, in a script whose whole
# job is to be trusted about a file.
probe() { ffprobe -v error -select_streams a:0 -show_entries "$1" -of csv=p=0:nk=1 "$f" | head -1; }
ch=$(probe stream=channels)
rate=$(probe stream=sample_rate)
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0:nk=1 "$f" | head -1)
printf '%s: %sch %sHz %.3fs\n' "$f" "$ch" "$rate" "$dur"
if [ "$ch" -lt 2 ]; then echo " single channel -- nothing to compare"; exit 0; fi
layout=5.1; [ "$ch" = 2 ] && layout=stereo
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
map=""; for i in $(seq 0 $((ch-1))); do map="$map -map [c$i] $tmp/c$i.wav"; done
split=""; for i in $(seq 0 $((ch-1))); do split="$split[c$i]"; done
# shellcheck disable=SC2086
ffmpeg -hide_banner -v error -y -i "$f" \
-filter_complex "channelsplit=channel_layout=$layout$split" $map
declare -a sums
for i in $(seq 0 $((ch-1))); do
s=$(ffmpeg -hide_banner -v error -i "$tmp/c$i.wav" -f md5 - | cut -d= -f2)
peak=$(ffmpeg -hide_banner -v info -i "$tmp/c$i.wav" -af astats -f null - 2>&1 \
| grep -m1 "Peak level dB" | sed 's/.*: //')
sums[i]="$s"
printf ' ch%-2d peak %-12s %s\n' "$i" "$peak" "$s"
done
dupes=0
for i in $(seq 0 $((ch-1))); do
for j in $(seq $((i+1)) $((ch-1))); do
if [ "${sums[i]}" = "${sums[j]}" ]; then
echo " 🔴 ch$i and ch$j are BYTE-IDENTICAL"
dupes=1
fi
done
done
# STARVATION: the second way a capture looks perfect and carries nothing.
#
# A monitor sink advances at WALL-CLOCK rate and substitutes silence whenever the
# producer is late. An emulator running below real time therefore yields a file
# of exactly the right duration, right channel count, no duplicated channels --
# and chopped into fragments with holes punched between them, thousands of times
# over. Envelope correlation against such a file is destroyed by construction:
# what dominates the envelope is the dropout schedule, not the content.
#
# Measured on the capture that prompted this: 35.6 % of frames silent on all six
# channels, 10 482 alternating runs, median burst 13.5 ms and median gap 3.9 ms
# -- a 17.4 ms period, 57 Hz. The Decoder measured the untruncated original at
# 39.3 % and 10 595 runs; the two agree.
#
# THE DISCRIMINATOR IS THE RUN STRUCTURE, NOT THE SILENCE FRACTION. Real audio is
# full of silence -- a voice track is more than half gaps -- but those are TENS of
# runs of HUNDREDS of milliseconds. Dropout chop is THOUSANDS of runs of a few
# milliseconds. So the test is: many short all-channel gaps.
set +e
python3 - "$f" <<'PYEOF'
import array, struct, sys
d = open(sys.argv[1], 'rb').read()
i, fmt, off = 12, None, None
while i + 8 <= len(d):
cid = d[i:i+4]; sz = struct.unpack('<I', d[i+4:i+8])[0]
if cid == b'fmt ': fmt = d[i+8:i+8+sz]
elif cid == b'data':
off, declared = i + 8, sz; break
i += 8 + sz + (sz & 1)
if fmt is None or off is None:
print(" (not a plain WAV -- starvation check skipped)"); raise SystemExit(0)
ch = struct.unpack('<H', fmt[2:4])[0]; rate = struct.unpack('<I', fmt[4:8])[0]
avail = len(d) - off
if declared == 0 or declared > avail:
# A streaming writer that never patched its header. The file may also be a
# copy taken while it was still being written -- which happened, and made a
# provenance claim wrong.
print(" ⚠️ data chunk declares %d bytes, %d present -- header never patched;"
% (declared, avail))
print(" treat the duration as unverified and check the file is complete.")
n = avail // (2 * ch)
a = array.array('h'); a.frombytes(d[off:off + n * 2 * ch])
sil = bytearray(n)
for f_ in range(n):
b = f_ * ch
if not any(a[b+c] for c in range(ch)): sil[f_] = 1
tot = sum(sil)
# A GAP IS A RUN, NOT A SAMPLE. The first version of this counted every frame
# whose channels were all exactly zero, and real audio crosses zero constantly --
# it scored a clean voice track at 5 947 "gaps" of median 0.0 ms and called it
# starved. The known-good control caught it. Only runs of at least 1 ms (48
# frames at 48 kHz) count: a zero-crossing is one sample, a dropout is hundreds.
MINGAP = max(1, rate // 1000)
runs_s, runs_n = [], []
cur, ln = sil[0], 0
for v in sil:
if v == cur: ln += 1
else:
(runs_s if cur else runs_n).append(ln); cur = v; ln = 1
(runs_s if cur else runs_n).append(ln)
runs_s = [r for r in runs_s if r >= MINGAP]
if not runs_s:
print(" all-channel silence 0.0% -- no gaps at all"); raise SystemExit(0)
rs = sorted(runs_s); med = 1000.0 * rs[len(rs)//2] / rate
secs = n / float(rate)
rate_per_s = len(runs_s) / secs
print(" all-channel silence %.1f%%, %d gap(s) over 1 ms (%.1f/s), median gap %.1f ms"
% (100.0*tot/n, len(runs_s), rate_per_s, med))
# THE THRESHOLD IS SET FROM CONTROLS, and the first two I invented were both
# wrong -- they failed real audio. Measured:
#
# the starved capture 32.9 gaps/s, median 3.9 ms, 35.6 % silent
# a real music+SFX bed 3.3 gaps/s, median 1.4 ms, 1.1 % silent
# a voice track, 53 % pauses 0.03 gaps/s
#
# Real audio does contain short all-zero runs -- a quiet passage in 16-bit is
# genuinely zero for milliseconds -- so neither the gap COUNT nor the median
# length separates them. The RATE does, by an order of magnitude in both
# directions, and 20/s sits between with a 1.6x margin below the bad case and
# 6x above the worst good one.
if rate_per_s >= 20.0 and med < 50.0:
print(" 🔴 STARVED: %.1f gaps per second at a median of %.1f ms."
% (rate_per_s, med))
print(" The producer was not keeping the sink fed.")
raise SystemExit(3)
PYEOF
starved=$?
set -e
if [ "$starved" = 3 ]; then
echo "FAIL: the recording is starved. A monitor sink advances at wall-clock rate"
echo " and substitutes silence when the producer is late, so this file has"
echo " the right duration and holes punched through the content. Correlation"
echo " against it is meaningless. See docs/port/AUDIO-VERIFICATION.md §7."
exit 1
fi
if [ "$dupes" = 1 ]; then
echo "FAIL: duplicated channels. A surround remap drops and duplicates silently;"
echo " channels are missing from this file. Do not analyse it -- fix the"
echo " sink's channel_map and re-record. See docs/port/AUDIO-VERIFICATION.md."
exit 1
fi
echo "PASS: no duplicated channels. (Necessary, not sufficient -- this says"
echo " nothing about whether the right thing was recorded.)"