. "$BST_LIB" # BST-TIMEOUT: 300 # a wasm probe to stage, then 6 encode and 6 decode passes # # over 20 frames across two fixtures, plus a corrupt-input # # pass. The ==BST-SUITE== line reports what it actually spent. # celt051 0.5.1.3+git (meta-networking recipes-support/celt051, SRCREV 5555aae8) -- # the standalone CELT codec: the low-delay transform codec that became the # high-band half of Opus, still linked by telephony and remote-desktop stacks # that predate RFC 6716. # # WHAT SHIPS, AND WHY THAT DICTATES A PROBE. The recipe's PACKAGECONFIG is empty # by default, so configure gets --disable-tools and celtenc051/celtdec051 are # never built -- the only reason the recipe has no build dependency at all is # that the tools, and with them libogg, are switched off. So this port installs # an archive, three headers and a pkg-config file and NO executable. Without a # cross-linked consumer this suite could assert only that those files exist, # which passes just as happily if every function in the archive traps on first # call. data/celt051-probe is src/celt051_probe.c linked against the very # archive this port installs. # # THE ORACLE QUESTION IS THE WHOLE DESIGN OF THIS FILE, AND IT WAS MEASURED # RATHER THAN ASSUMED. 57-faad2.test may compare against a stored x86-64 decode # because faad2's output was measured byte-identical across gcc 9.5, clang 10 and # gcc -O0; 57-libvorbis.test and 57-speexdsp.test both REFUSE a stored # cross-machine digest; and 57-libopus.test -- the closest relative, since Opus # contains CELT -- pins a digest observed ON TARGET at port time rather than one # imported from x86-64. Three neighbours, three different answers, each right # about its own subject. So the question was put to celt051 directly: the same # sources were built nine ways on x86-64 (-O0, -O2, -O3, -Os, -ffloat-store, # -ffp-contract=off, -ffp-contract=fast, -fno-fast-math, -march=native) and the # probe transcripts compared. # # EIGHT OF THE NINE ARE BYTE-IDENTICAL. Optimisation level does not move this # codec's output, and neither does -ffp-contract in either direction. # # THE NINTH, -march=native, MOVES IT -- and it is the one that enables FMA. # Exactly one encoded frame changes (mono frame 06) and the decoded PCM changes # more widely, including in frames whose bitstream did NOT change, which places # the sensitivity in the float synthesis path and not only in the encoder's # quantisation decisions. # # AND THEN THE TARGET SETTLED IT. wasm32 has no FMA -- the MVP has no # fused-multiply-add instruction and the spec forbids contraction -- and the # on-target run reproduces data/celt051-native.txt BYTE FOR BYTE: all 40 encoded # bitstream frames in hex, the decoded PCM digests, the per-frame energies. The # one thing the x86-64 sweep could not settle -- libm, since all nine variants # linked the same glibc, and celt051 calls exp, log, pow, sqrt, sin and cos on # the mode-construction and band-energy paths -- came out identical too. So the # ticket's premise holds here, as a measurement rather than an assumption. # # THE COMPARISON IS STILL LAYERED, because a future toolchain or libm change can # move it and the reader then needs to know WHICH way it moved: # # celt051/native-identical the whole transcript byte for byte -- the bar the # ticket asks for, and what this port achieves # celt051/native-structural every line that cannot drift: mode geometry, # lookahead, the negative controls, packet lengths, # the determinism flags, the input digests. A red # HERE is a real port defect and is never last-ulp # celt051/native-bitstream the encoded frames alone # celt051/native-pcm-bounded the decoded energy within 1e-6 relative # # READING A RED. native-structural red => the port is broken; nothing about # floating point excuses it. native-identical red with structural, bitstream and # pcm-bounded all green => this arm's libm has moved off x86-64's in the last # ulp; a finding to record and re-reference, not a broken codec. bitstream red # with structural green => a quantisation decision flipped, the FMA-class effect # above, and worth attributing before it is accepted. # # DETERMINISM IS GRADED SEPARATELY AND UNCONDITIONALLY. mono/stereo # -enc-repeat-identical and -dec-repeat-identical come from a second encoder and # decoder built fresh over the same input inside the probe. That property is owed # by any codec on any arm whatever its libm does, so it is strict here even if # the cross-machine comparison ever stops being. # # THE FIXTURES ARE NOT TONES. src/celt051_mkfixture.py walks four regimes in one # file -- steady tones, digital silence, a hard transient, then tones plus # deterministic LCG noise -- because CELT has a transient detector, a pitch # predictor and per-band energy quantisation that a single steady sine leaves # almost entirely unexercised. The stereo fixture's right channel is the left # delayed and inverted, so the two channels are correlated but not identical: two # identical channels would let a decoder that ignored the side channel pass. # # STDERR IS NOT EMPTY HERE, AND THAT IS ASSERTED RATHER THAN IGNORED. celt_warning # writes to stderr, and the probe deliberately provokes it: eight rejected # mode_create calls and ten corrupted frames. The exact counts are graded below, # which turns what would be unexplained noise into coverage of the diagnostic # path. An UNEXPECTED stderr line still reds the run. # # THE ENCODER ONLY WORKS BECAUSE ONE FILE IS HELD AT -O0. alg_quant traps on # `unreachable` in the coroutinize pass's .resume half when libcelt/vq.c is built # at -O1 or above for this arm, which kills every celt051_encode() call. The # bbappend patches that one translation unit; everything this suite drives # outside vq.c still runs at -O2 -flto. Tracked as brintos/hwjs-cc#65 -- if that # workaround is ever dropped while #65 is open, this suite goes red at # celt051/probe-rc with exit 132, which is the intended alarm and not a # regression in the codec. # # NO SOUND DEVICE, NO NETWORK, NO PEER. Everything is files and arithmetic. PROBE=$BST_DATA/celt051-probe NATIVE=$BST_DATA/celt051-native.txt # --------------------------------------------------------- what the port ships check celt051/lib-static test -f /usr/lib/libcelt051.a check celt051/header-celt test -f /usr/include/celt051/celt.h check celt051/header-types test -f /usr/include/celt051/celt_types.h check celt051/header-hdr test -f /usr/include/celt051/celt_header.h check celt051/pkgconfig test -f /usr/lib/pkgconfig/celt051.pc # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape celt051/static-only "/usr/lib/libcelt051*.so*" # --disable-tools is the reason this recipe has no dependency on libogg. If the # tools ever appear, the dependency came with them and the "no build dependency # at all" premise of this port is gone -- so their ABSENCE is a real assertion. if [ -x /usr/bin/celtenc051 ] || [ -x /usr/bin/celtdec051 ]; then bst_notok celt051/tools-disabled "celtenc051/celtdec051 present -- libogg pulled in?" else bst_ok celt051/tools-disabled fi # Version comes from configure.ac (0.5.1.3); the recipe's PV adds a +git suffix # that never reaches the .pc. Pinned against the shipped file rather than against # what a native ./configure prints. check_out celt051/pc-version "Version: 0.5.1.3" cat /usr/lib/pkgconfig/celt051.pc # celt051.pc.in writes `Libs: -L${libdir} -lcelt051 -lm`. The -lm matters: the # float build genuinely needs libm, and a consumer linking without it fails at # link time rather than at run time. check_out celt051/pc-libs "-lcelt051 -lm" cat /usr/lib/pkgconfig/celt051.pc # Upstream's Cflags is a bare -I${includedir}, NOT -I${includedir}/celt051, so a # consumer includes . Both are pinned against the SHIPPED file: # the .pc keeps pkg-config variable references in Libs/Cflags even though Yocto # resolves prefix/libdir/includedir above them, and asserting the resolved form # here would be a false red against a perfectly correct file. check_out celt051/pc-cflags "Cflags: -I\${includedir}" cat /usr/lib/pkgconfig/celt051.pc # ------------------------------------------- the probe and its control ship --- if [ ! -f "$PROBE" ]; then bst_notok celt051/probe-present "$PROBE missing -- the port cannot be functionally verified" exit 0 fi bst_ok celt051/probe-present if [ ! -f "$NATIVE" ]; then bst_notok celt051/native-present "$NATIVE missing -- no oracle to compare against" exit 0 fi bst_ok celt051/native-present # The rootfs is read-only at browser runtime, so the probe cannot be executed # where it lies -- stage an executable copy, as every probe-driven suite does. tmp=${TMPDIR:-/tmp}/bst-celt051.$$ mkdir -p "$tmp" 2>/dev/null _p=$tmp/celt051-probe if ! cp "$PROBE" "$_p" 2>/dev/null || ! chmod +x "$_p" 2>/dev/null; then bst_notok celt051/probe-exec "could not stage an executable copy of the probe" rm -rf "$tmp" 2>/dev/null exit 0 fi bst_ok celt051/probe-exec "$_p" "$BST_DATA" > "$tmp.out" 2> "$tmp.err"; rc=$? # rc=132 here is the hwjs-cc#65 trap in alg_quant, not a codec fault -- see the # header. Any other non-zero is the probe itself failing. check_eq celt051/probe-rc 0 "$rc" # Checked BEFORE anything is read out of the transcript: a probe killed halfway # leaves a prefix of correct-looking lines, and every assertion below would then # be grading a file that stops early. if [ "$(tail -n 1 "$tmp.out" 2>/dev/null)" = "CELT-PROBE-END" ]; then bst_ok celt051/probe-complete else bst_notok celt051/probe-complete "no PROBE-END: $(_bst_1line "$(tail -n 3 "$tmp.out" 2>/dev/null)")" fi # One awk pass turns every `KEY value` line into a shell variable, so the ~130 # assertions below cost no further processes. Per-case `check_out` spawns are the # dominant cost on this runtime -- the 81-cjson / 81-qcbor lesson, restated by # 81-msgpack-c. eval "$(awk '{k=$1; gsub(/[^A-Za-z0-9]/,"_",k); $1=""; sub(/^ /,""); gsub(/'"'"'/,"",$0); printf "v_%s='"'"'%s'"'"'\n", k, $0}' "$tmp.out")" want() { check_eq "celt051/$1" "$2" "$3"; } # ------------------------------------------------------------ mode geometry --- # Every mode across the supported range (32-96 kHz, mono/stereo, frame 64-512 # even). The lookahead is the codec's algorithmic delay and a consumer that # mis-times playback depends on it, so it is pinned rather than merely printed. want mode-32k-128 "framesize 128 channels 1 lookahead 64" "$v_mode_m32k_128" want mode-44k-256 "framesize 256 channels 1 lookahead 128" "$v_mode_m44k_256" want mode-44k2-256 "framesize 256 channels 2 lookahead 128" "$v_mode_m44k2_256" want mode-48k-512 "framesize 512 channels 1 lookahead 128" "$v_mode_m48k_512" want mode-96k-64 "framesize 64 channels 1 lookahead 32" "$v_mode_m96k_64" # ---------------------------------------------------------- argument checks --- # All eight must be refused with CELT_BAD_ARG (-1) and a NULL mode. The permuted # case is upstream's own claim -- modes.c says "permutation of the arguments will # automatically be invalid" -- and is the one that would catch a build whose # parameter order was reordered. want neg-fs-low "NULL err -1" "$v_neg_fs_low" want neg-fs-high "NULL err -1" "$v_neg_fs_high" want neg-ch-three "NULL err -1" "$v_neg_ch_three" want neg-ch-neg "NULL err -1" "$v_neg_ch_neg" want neg-frame-odd "NULL err -1" "$v_neg_frame_odd" want neg-frame-tiny "NULL err -1" "$v_neg_frame_tiny" want neg-frame-big "NULL err -1" "$v_neg_frame_big" want neg-permuted "NULL err -1" "$v_neg_permuted" # --------------------------------------------------------------- the inputs --- # The fixture digests are asserted so that a difference further down is known to # be the codec's and not a fixture that failed to ship or was truncated. want mono-fixture-bytes "10240" "$v_mono_fixture_bytes" want mono-fixture-digest "a0a6e18f33e22eaa" "$v_mono_fixture_digest" want stereo-fixture-bytes "20480" "$v_stereo_fixture_bytes" want stereo-fixture-digest "3f8c25560afc9121" "$v_stereo_fixture_digest" # Input energy is a pure integer sum over the fixture, so it is exact on any arm. want mono-in-energy "624921959006" "$v_mono_in_energy" want stereo-in-energy "970810802611" "$v_stereo_in_energy" # ------------------------------------------------------- mode + CBR framing --- want mono-mode-err "0" "$v_mono_mode_err" want mono-mode-framesize "256" "$v_mono_mode_framesize" want mono-mode-channels "1" "$v_mono_mode_channels" want mono-mode-lookahead "128" "$v_mono_mode_lookahead" want mono-bitstream-version "80000006" "$v_mono_bitstream_version" want stereo-mode-channels "2" "$v_stereo_mode_channels" # CELT 0.5.1 is constant-bitrate: every frame is exactly the byte count asked # for, so 20 frames at 48 bytes is 960 on any correct build. A short total means # a frame refused to encode, which the probe would also have named. want mono-enc-bytes "960" "$v_mono_enc_bytes" want stereo-enc-bytes "960" "$v_stereo_enc_bytes" # ------------------------------------------------------------- determinism ---- # Owed unconditionally, whatever the arm's libm does. A fresh encoder over the # same input must produce the same bytes; a fresh decoder over the same bytes # must produce the same samples. want mono-enc-repeat "1" "$v_mono_enc_repeat_identical" want mono-dec-repeat "1" "$v_mono_dec_repeat_identical" want stereo-enc-repeat "1" "$v_stereo_enc_repeat_identical" want stereo-dec-repeat "1" "$v_stereo_dec_repeat_identical" # --------------------------------------------------- decode sanity, on shape -- # These say the decode produced a signal of the right shape without asserting its # exact bytes, so they hold even if the last-ulp comparison below ever stops # holding. A decoder that returned silence, or full-scale noise, fails here. # # Every default below is chosen so an UNSET value FAILS: the -gt cases default to # 0 and the -lt cases to a number above their bound. An earlier draft defaulted a # -lt case to 0, which meant a probe that died before emitting the key scored a # PASS -- the one direction a missing measurement must never take. if [ "${v_mono_dec_maxabs:-0}" -gt 8000 ] && [ "${v_mono_dec_maxabs:-0}" -le 32767 ]; then bst_ok celt051/mono-dec-plausible else bst_notok celt051/mono-dec-plausible "maxabs=${v_mono_dec_maxabs:-unset}" fi # WHICH FRAME IS "THE SILENT ONE" WAS MEASURED, NOT COUNTED OFF THE FIXTURE. The # fixture goes silent at frame 5, but the codec's 128-sample lookahead is half a # frame, so frame 5 still carries the tone's tail (1.6e10) and frame 6 the # decoder's ringing (4.6e7). Frame 7 is the first that is actually silent (240). # Asserting silence at frame 5 or 6 -- the obvious reading of the fixture -- would # have been a false red against a perfectly correct decoder. if [ "${v_mono_dec_e00:-0}" -gt 1000000000 ]; then bst_ok celt051/mono-frame00-tonal else bst_notok celt051/mono-frame00-tonal "e00=${v_mono_dec_e00:-unset}"; fi if [ "${v_mono_dec_e06:-999999999999}" -lt 1000000000 ]; then bst_ok celt051/mono-frame06-decaying else bst_notok celt051/mono-frame06-decaying "e06=${v_mono_dec_e06:-unset}"; fi if [ "${v_mono_dec_e07:-999999999999}" -lt 1000000 ]; then bst_ok celt051/mono-frame07-silent else bst_notok celt051/mono-frame07-silent "e07=${v_mono_dec_e07:-unset}"; fi # The transient frame must be the loudest in the file -- a decoder that smeared # it into its neighbours would still satisfy every whole-stream measure. if [ "${v_mono_dec_e08:-0}" -gt "${v_mono_dec_e01:-0}" ]; then bst_ok celt051/mono-frame08-transient else bst_notok celt051/mono-frame08-transient "e08=${v_mono_dec_e08:-unset} e01=${v_mono_dec_e01:-unset}"; fi if [ "${v_mono_dec_e15:-0}" -gt 1000000000 ]; then bst_ok celt051/mono-frame15-noisy else bst_notok celt051/mono-frame15-noisy "e15=${v_mono_dec_e15:-unset}"; fi # ------------------------------------------------ corrupt input and loss ------ # CELT 0.5.1 has no checksum, so the decoder is not required to DETECT damage -- # only to stay inside its buffers and return. It happens to report all ten # corrupted frames, which is pinned; a build that starts accepting them silently # is a change worth noticing. want corrupt-survived "1" "$v_corrupt_survived" want corrupt-nonok "10" "$v_corrupt_nonok_frames" # A NULL/zero-length packet is the packet-loss path: concealment, not an error. want loss-null-rc "0" "$v_loss_null_rc" want loss-null-maxabs "0" "$v_loss_null_maxabs" # ------------------------------------------ the layered x86-64 comparison ----- # See the header for why this is four cases and not one cmp. if cmp -s "$tmp.out" "$NATIVE"; then bst_ok celt051/native-identical # When the whole transcript matches, the three below match BY CONSTRUCTION. # Saying so without paying for three more awk passes over 133 lines. bst_ok celt051/native-structural bst_ok celt051/native-bitstream bst_ok celt051/native-pcm-bounded else d=$(diff "$tmp.out" "$NATIVE" 2>/dev/null | grep -c '^<') bst_notok celt051/native-identical "$d transcript lines differ from the x86-64 control" # Structural: every line whose value cannot drift with floating point. A red # here is a port defect and nothing to do with libm. _strip='/-enc-frame|-enc-digest|-dec-digest|-dec-maxabs|-dec-nonzero|-dec-energy|-dec-e[0-9]|corrupt-maxabs/ {next} {print}' if awk "$_strip" "$tmp.out" > "$tmp.s1" && awk "$_strip" "$NATIVE" > "$tmp.s2" && cmp -s "$tmp.s1" "$tmp.s2"; then bst_ok celt051/native-structural else bst_notok celt051/native-structural "$(_bst_1line "$(diff "$tmp.s1" "$tmp.s2" 2>/dev/null | head -n 4)")" fi # The encoded bitstream alone. if grep '\-enc-frame' "$tmp.out" > "$tmp.b1" 2>/dev/null && grep '\-enc-frame' "$NATIVE" > "$tmp.b2" 2>/dev/null && cmp -s "$tmp.b1" "$tmp.b2"; then bst_ok celt051/native-bitstream else nb=$(diff "$tmp.b1" "$tmp.b2" 2>/dev/null | grep -c '^<') bst_notok celt051/native-bitstream "$nb of 40 encoded frames differ" fi # Decoded energy within 1e-6 relative. Integer arithmetic only: 57-speexdsp # found busybox awk's sqrt/log silently absent, yielding an EMPTY number that # takes the whole expression with it and reports a blank where the measurement # should be. A ratio of integers cannot produce one. # # Written as d <= want/1000000 rather than d*1000000 <= want: awk computes in # doubles, and these energies are ~6e11, so the multiplied form reaches 6e17 -- # past 2^53, where integers stop being exact and the comparison silently # becomes a coin toss. The divided form stays well inside the exact range. ne=$(awk -v have="${v_mono_dec_energy:-}" ' $1=="mono-dec-energy" { if (have == "") { print "probe emitted no mono-dec-energy"; exit } want=$2; d = have > want ? have - want : want - have; print (d <= want/1000000) ? "ok" : "off by " d " of " want; }' "$NATIVE") case "$ne" in ok) bst_ok celt051/native-pcm-bounded ;; *) bst_notok celt051/native-pcm-bounded "$(_bst_1line "$ne")" ;; esac fi # ------------------------------------------------------------------ stderr ---- # Not empty, and asserted rather than ignored: the probe provokes celt_warning # eight times through rejected mode_create calls and ten times through corrupted # frames. Any line that is not one of those three known warnings reds the run. nerr=$(wc -l < "$tmp.err" 2>/dev/null | tr -d ' ') want stderr-lines "18" "$nerr" nunk=$(grep -cv '^warning: \(Sampling rate must be\|Only mono and stereo\|Only even frame sizes\|decode error\)' "$tmp.err" 2>/dev/null) want stderr-unexpected "0" "$nunk" # ------------------------------------------------------- anti-truncation ------ # The probe counts its own emitted lines. If this matches and PROBE-END is # present, the greens above are a statement that the probe RAN, not merely that # the file it wrote reached its end. want probe-cases "130" "$v_CELT_cases" rm -rf "$tmp" "$tmp".* 2>/dev/null