. "$BST_LIB" # BST-TIMEOUT: 300 # measured 61s over 121 cases; ~40 execs of a wasm module, # # each a fresh instantiation, plus ten awk walks over the # # Ogg streams. 300 is the value the ticket gate and the # # nightly already pass, so it costs them nothing, and it is # # the net for a bare `brintos-selftest` whose 60s default # # would leave no headroom over a figure measured on an idle # # host. # speex 1.2.1 (poky recipes-multimedia/speex) -- the Speex speech codec: the # narrowband/wideband/ultra-wideband CELP coder, /usr/lib/libspeex.a, the headers # under /usr/include/speex, and the two command-line tools `speexenc` and # `speexdec`. Both halves of the codec are on this image, so unlike 57-liba52 # and 57-faad2 -- decode-only ports that can be given nothing but pre-encoded # bitstreams -- this suite ROUND-TRIPS: it encodes on target and compares the # BITSTREAM, then decodes and compares the PCM. # # EVERYTHING RUNS OFFLINE. speexenc reads a WAV and writes a file; speexdec with # an output filename writes a WAV. Neither touches an audio device on those # paths. speexdec with NO output file does try to play, and that path is pinned # below rather than avoided. # # THE ENCODER IS NOT BYTE-DETERMINISTIC, AND THAT IS UPSTREAM. speexenc.c:539 # seeds the Ogg BITSTREAM SERIAL NUMBER with `srand(time(NULL)); rand()`. The # serial sits at offset 14 of every page header and the page CRC at offset 22 # covers it, so two runs of the same binary on the same input differ in exactly # those 8 bytes per page and agree on every other byte -- measured at 24 bytes # over 3 pages on x86-64. Every bitstream comparison here is therefore made on # the CANONICAL form: ogg_canon() below walks the page chain and zeroes both # fields on each side. This is the same walk src/speex_mkfixtures.py performs in # Python. # # THE CANONICALISER IS ITSELF TESTED, WHICH IS NOT OPTIONAL. A mask that is too # WIDE makes every comparison trivially pass -- an awk that mis-walked the # segment table and zeroed the whole file would turn this suite green and keep it # green through any regression. So speex/canon-pages and speex/canon-mask-width # assert that the walk finds exactly 3 pages and changes exactly 8 bytes per # page, 24 in total, before any comparison relies on it. Those two are the # instrument's own calibration; if they are red, nothing below them means # anything. # # WHAT IS DELIBERATELY *NOT* ASSERTED: that two successive encodes DIFFER. They # usually do, but `srand(time(NULL))` is seeded from a one-second clock, so two # back-to-back runs inside the same second draw the SAME serial and produce # byte-identical files. A case asserting a difference would be red whenever the # pair straddled no second boundary -- a coin-flip red with no defect behind it. # What is asserted instead is the property that actually matters and is always # true: successive encodes are CANONICALLY identical (speex/enc-repeat-canon). # # THE STORED REFERENCES ARE AN ORACLE, AND THAT WAS MEASURED BEFORE IT WAS # RELIED ON. Two neighbouring codec suites (57-libvorbis, 57-speexdsp) refuse a # stored cross-machine digest and are right about their own subjects. speex was # measured, not assumed: the same 1.2.1 source built three ways on x86-64 -- gcc # 9.5 -O2, gcc 9.5 -O0 and clang 10 -O2, against one libogg 1.3.5 and one # speexdsp 1.2.1 -- produced byte-identical canonical encodes and byte-identical # decodes for all five fixtures. speex does call libm on live paths (acos in # lsp.c's X2ANGLE, sqrt in filters.c's compute_rms, log/pow in vbr.c), but each # result feeds a VECTOR-QUANTISER INDEX or a coarse mode decision, so a last-ulp # disagreement is swallowed by the quantiser unless it falls exactly on a # decision boundary. When cmp says equal the graded cases are 0 BY CONSTRUCTION; # when it says unequal they run for real and bound the damage. # # THE FIVE FIXTURES, EACH FOR A PATH THE OTHERS DO NOT REACH. All are built by # src/speex_mkfixtures.py from a 400 Hz fundamental plus two harmonics under a # 25 Hz envelope -- exactly periodic at 400 Hz, so the folds below are exact. # # nb8k 8 kHz mono nb_celp.c alone, the narrowband CELP core # wb16k 16 kHz mono sb_celp.c: one QMF split-band layer stacked over it # uwb32k 32 kHz mono sb_celp.c stacked a second time # st16k 16 kHz stereo stereo.c -- intensity stereo, encode and decode # vbr8k 8 kHz mono vbr.c, whose log()/pow() are DEAD in the other four # # THE STEREO FIXTURE ASSERTS A BALANCE, NOT A SEPARATION, AND THAT IS THE FORMAT # TALKING. Speex stereo is INTENSITY stereo: the encoder codes a mono downmix # plus a balance, so the two decoded channels are scaled copies of one signal and # a suite asking "does L carry a tone R does not" would be asserting something # the bitstream cannot represent. The fixture is one signal at two levels, 2:1 in # amplitude, and what is checked is that the 4:1 power ratio survived the # round trip (measured 4.417) and that the channels are not identical. # # THE SIGNAL CASES USE AN INTEGER PERIOD-FOLD AND NO TRIG AT ALL. 400 Hz has an # exact integer period at every rate here (20 / 40 / 80 samples) and each divides # the 6400-frame window exactly. The control fold is 25, which keeps only periods # dividing 25 -- 320 Hz and 1600 Hz at 8 kHz -- and none of the fixtures contains # one. No sqrt, sin or cos is needed: 57-speexdsp.test found awk's math support # silently absent under busybox awk, where those yield an EMPTY number and take # the whole expression with them, producing a red that carries a blank where the # measurement should be. # # READING A RED. # * EVERY encode case red at once, with the decode cases green, is not five # regressions -- it is one build-configuration change. poky's recipe reads # `bb.utils.contains('TARGET_FPU','soft', '--enable-fixed-point # --disable-float-api --disable-vbr', '', d)`, so a machine that grew a soft # TARGET_FPU would rebuild speex as a FIXED-POINT codec whose bitstream is # legitimately different from every reference here. Check TARGET_FPU before # reading it as a port defect. This arm's TUNE_FEATURES is "wasm32" and sets # no TARGET_FPU, so the float path is what is built and what these references # describe. # * A red on speex/canon-pages or speex/canon-mask-width invalidates every # bitstream case above and below it; fix the walk first. # * A red on *-pcm-exact alone with the GRADED PAIR green is last-bit float # drift and is EXPECTED on this target -- see the decoder-drift note below. # A red on the graded pair too is an arithmetic fault, and the first thing to # suspect is the scratch allocator, not libm (below). # * ALL FIVE *-enc-canonical red at once, or any *-enc-canonical red with the # decode cases behaving, points at the SCRATCH ALLOCATOR rather than at # arithmetic: speex is built with -DSPEEX_NO_DYNAMIC_STACK, and if the # patch's second hunk is ever dropped the codec runs with st->stack = NULL # and writes through NULL + offset. wasm32 does not trap that, so it returns # very nearly correct audio instead of crashing. The tell is an # *-enc-canonical red (uwb32k first) while every graded decode case sits # exactly where it sits today. # * A wrong *-channels, *-rate, *-bits or *-data-size is a decoder or packaging # fault and nothing else; those are integer and cannot drift. # * A red on speex/vbr-silence-collapses with the other VBR cases green means # --disable-vbr reached the build (see TARGET_FPU above). # * A red on the fold cases with the byte comparisons green is impossible and # means this suite is broken. # THE DECODER DRIFTS FROM THE x86-64 REFERENCE BY 1 LSB AND THE ENCODER DOES # NOT, WHICH IS THE POINT. Measured on wasm32-hwjs glibc, 2026-08-15, with # speex 1.2.1 and the two-hunk stack_alloc patch applied: # # fixture enc canonical decode max |delta| samples differing # nb8k exact 1 LSB 149 / 8040 (1.85%) # wb16k exact 1 LSB 369 / 8080 (4.57%) # uwb32k exact 1 LSB 341 / 8320 (4.10%) # st16k exact 2 LSB 524 / 16160 (3.24%) # vbr8k exact 1 LSB 122 / 8040 (1.52%) # # The ENCODER is byte-exact on all five, and that is the null control for the # decode reds: the encoder runs the same float code, but every result it # produces lands in a VECTOR-QUANTISER INDEX, so a sub-threshold difference is # absorbed and a real arithmetic fault would NOT be -- it would flip a quantiser # decision and show up as an *-enc-canonical red. Five fixtures across three # codec paths flip none. What is left is last-ulp float difference exposed by # the decoder's CONTINUOUS output, where nothing quantises it away. It is also # deterministic and allocator-independent: the five counts above are bit-identical # before and after the stack_alloc fix, which changed only uwb32k's encode. # # st16k IS BOUNDED AT 2 LSB, NOT 1, AND THAT IS THE FORMAT. Speex stereo codes a # mono downmix plus a balance, and this fixture is one signal at two levels 2:1 # in amplitude, so a 1-LSB drift in the downmix is scaled up into the louder # channel and lands as 2. The bound is per-fixture (roundtrip's 7th argument) # rather than global precisely so this stays a stated exception. # # PCM_DRIFT_PCT BOUNDS HOW WIDESPREAD THE DRIFT MAY BE, and it is a FRACTION # because the count is not comparable across fixtures -- st16k has twice the # samples of nb8k. The old bound was a flat 64 samples, which was set before any # target measurement existed and is 0.8% of nb8k but 0.4% of st16k. 8% is ~1.75x # the worst measured (4.57%), which leaves room for the drift to move with a # toolchain or libm change without leaving room for corruption: the NULL-stack # defect above, the only real fault this suite has ever caught, moved # *-enc-canonical rather than these counts, and anything that scribbles on the # decode buffer blows past 8% immediately. Dated snapshot -- a later bound change # owes a fresh measurement, not a re-reading of this one. # # BRINTOS-WORKAROUND: glibc#7 revisit -- PIN-GATED, BRINTOS-2330: trigger is the pin, not this issue -- our glibc is brintium's gitlink 8f4ac10c on joel-dev, forked from wasm32 at 0ee15592 (2026-09-04); a fix on either branch need not be in the tree we build. Read that tree before removing. # the graded pair exists, and *-pcm-exact # and *-roundtrip are expected red, only because wasm32 float results differ from # x86-64 by 1 ulp on the decoder's continuous output. If that is answered # "expected, not a defect", keep these bounds and delete this marker; if the # arithmetic is ever made to match, drop the graded pair back to a byte compare. PCM_DRIFT_PCT=8 require speexenc speex/present tmp=${TMPDIR:-/tmp}/bst-speex.$$ mkdir -p "$tmp" 2>/dev/null size() { wc -c < "$1" 2>/dev/null | tr -d ' '; } md5of() { md5sum "$1" 2>/dev/null | cut -d' ' -f1; } have_md5=0; command -v md5sum >/dev/null 2>&1 && have_md5=1 # --------------------------------------------------------------- what shipped --- check speex/dec-present test -x /usr/bin/speexdec check speex/enc-present test -x /usr/bin/speexenc check speex/lib-static test -f /usr/lib/libspeex.a check speex/header-speex test -f /usr/include/speex/speex.h check speex/header-bits test -f /usr/include/speex/speex_bits.h check speex/header-header test -f /usr/include/speex/speex_header.h check speex/header-callbacks test -f /usr/include/speex/speex_callbacks.h check speex/header-stereo test -f /usr/include/speex/speex_stereo.h check speex/pkgconfig test -f /usr/lib/pkgconfig/speex.pc bst_lib_shape speex/static-only "/usr/lib/libspeex*.so*" # The banner carries a __DATE__ ("compiled Aug 14 2026"), so only the version is # matched -- pinning the whole line would go red on every rebuild. v=$(speexenc --version 2>&1 | head -n 1) case "$v" in *"1.2.1"*) bst_ok speex/enc-version ;; *) bst_notok speex/enc-version "$(_bst_1line "$v")" ;; esac v=$(speexdec --version 2>&1 | head -n 1) case "$v" in *"1.2.1"*) bst_ok speex/dec-version ;; *) bst_notok speex/dec-version "$(_bst_1line "$v")" ;; esac # ------------------------------------------------------------------ payloads --- # Pinned BEFORE anything is encoded or decoded. Without this a later mismatch has # two indistinguishable causes -- the codec changed, or the payload did -- and # the wrong one gets reported. pin() { # pin if [ "$have_md5" -eq 1 ]; then check_eq "$1" "$2" "$(md5of "$3")" else bst_skip "$1" "no md5sum on this image"; fi } pin speex/fixture-nb8k-digest 139ffa871ab9d06648086ee06ece86a6 "$BST_DATA/speex-nb8k.wav" pin speex/fixture-wb16k-digest 825ef1b1ecd6b81f7bb72bbda2ffe67f "$BST_DATA/speex-wb16k.wav" pin speex/fixture-uwb32k-digest 85eecb256bc68a9ddfabb314659d5179 "$BST_DATA/speex-uwb32k.wav" pin speex/fixture-st16k-digest c1d13c6a72468c295ecd082845fe62fc "$BST_DATA/speex-st16k.wav" pin speex/fixture-vbr8k-digest 91fe7a39c1821942a329692431099a22 "$BST_DATA/speex-vbr8k.wav" pin speex/reference-nb8k-spx 4d3196e3ea8e103a5c7dab4521ebc673 "$BST_DATA/speex-nb8k.ref.spx" pin speex/reference-wb16k-spx 7db4e737a90328a7c849319078ce9815 "$BST_DATA/speex-wb16k.ref.spx" pin speex/reference-uwb32k-spx 2e4b1486a1bc5634cd43a84043d6fa8b "$BST_DATA/speex-uwb32k.ref.spx" pin speex/reference-st16k-spx 0cf68711cc6910e40c92b255de8a9611 "$BST_DATA/speex-st16k.ref.spx" pin speex/reference-vbr8k-spx 6162f9058f0551bf39a6fe7d3b1809a4 "$BST_DATA/speex-vbr8k.ref.spx" pin speex/reference-nb8k-wav dce72294849f36e98c5833738591dd1c "$BST_DATA/speex-nb8k.ref.wav" pin speex/reference-wb16k-wav 8e26563fa9434b3b3a7d7c4598eaead3 "$BST_DATA/speex-wb16k.ref.wav" pin speex/reference-uwb32k-wav 71a8e7497169dcbe2eafd24362e58447 "$BST_DATA/speex-uwb32k.ref.wav" pin speex/reference-st16k-wav 5308e9748ed13c67de7d6e6a0e658e1b "$BST_DATA/speex-st16k.ref.wav" pin speex/reference-vbr8k-wav 1cab01466acbd37f4e0979a11178f34b "$BST_DATA/speex-vbr8k.ref.wav" for f in nb8k wb16k uwb32k st16k vbr8k; do if [ ! -f "$BST_DATA/speex-$f.wav" ] || [ ! -f "$BST_DATA/speex-$f.ref.spx" ] \ || [ ! -f "$BST_DATA/speex-$f.ref.wav" ]; then bst_notok speex/payloads "missing one of $BST_DATA/speex-$f.{wav,ref.spx,ref.wav} -- remaining cases not run" rm -rf "$tmp"; exit 0 fi done # ------------------------------------------------------- the Ogg canonicaliser -- # Walk the page chain -- "OggS", 27-byte header, page_segments at +26, then that # many segment-table bytes whose sum is the body length -- and print the stream # one decimal byte per line with the serial (offset 14, 4 bytes) and the CRC # (offset 22, 4 bytes) of every page zeroed. Writing the page COUNT to a separate # file rather than into the byte stream keeps the stream comparable with cmp. ogg_canon() { # ogg_canon od -An -v -tu1 "$1" 2>/dev/null | awk -v pc="$3" ' { for (i = 1; i <= NF; i++) b[n++] = $i } END { off = 0; pages = 0 while (off < n) { if (b[off] != 79 || b[off+1] != 103 || b[off+2] != 103 || b[off+3] != 83) { print "BADPAGE " off > "/dev/stderr"; print -1 > pc; exit 1 } nseg = b[off+26]; body = 0 for (j = 0; j < nseg; j++) body += b[off+27+j] for (k = 14; k < 18; k++) b[off+k] = 0 for (k = 22; k < 26; k++) b[off+k] = 0 off += 27 + nseg + body; pages++ } print pages > pc for (i = 0; i < n; i++) print b[i] }' > "$2" } ogg_raw() { od -An -v -tu1 "$1" 2>/dev/null | awk '{for(i=1;i<=NF;i++) print $i}'; } # Both streams above are one DECIMAL VALUE PER LINE, so the count of disagreeing # bytes is a count of differing LINES and `cmp -l` is the wrong tool for it: # byte 204 and byte 0 render three characters wide and one, so every later line # shifts and cmp reports the whole tail. (The first cut used cmp -l and the dry # run read 6306 differences where there are 24. `cmp -s` for pure equality is # still exact -- identical line sequences are identical files -- and is kept.) ndifflines() { awk 'NR==FNR{a[FNR]=$0;next} a[FNR]!=$0{n++} END{print n+0}' "$1" "$2"; } # --- the instrument's calibration. Everything below depends on these two. --- ogg_canon "$BST_DATA/speex-nb8k.ref.spx" "$tmp/cal.canon" "$tmp/cal.pages" check_eq speex/canon-pages 3 "$(cat "$tmp/cal.pages" 2>/dev/null)" ogg_raw "$BST_DATA/speex-nb8k.ref.spx" > "$tmp/cal.raw" check_eq speex/canon-mask-width 24 "$(ndifflines "$tmp/cal.raw" "$tmp/cal.canon")" # ------------------------------------------------------- header field reads ---- # The 44-byte canonical RIFF/WAVE header, read one byte at a time so this works # under any od the image ships. The three u32 fields are printed with %.0f and # not %u: BUSYBOX AWK, which is the awk on this image, saturates %u and %d at the # signed 32-bit boundary while carrying the value correctly as a double. No field # here exceeds 2^31, but the neighbouring suite was bitten by exactly this and # the conversion is byte-identical for small values, so there is no reason to # take the risk. hdr() { # hdr -> "magic wave channels rate bits riffsize datasize" od -An -v -tu1 -N 44 "$1" 2>/dev/null | awk ' { for (i = 1; i <= NF; i++) b[n++] = $i } END { printf "%c%c%c%c %c%c%c%c %d %.0f %d %.0f %.0f\n", b[0], b[1], b[2], b[3], b[8], b[9], b[10], b[11], b[22] + 256*b[23], b[24] + 256*b[25] + 65536*b[26] + 16777216*b[27], b[34] + 256*b[35], b[4] + 256*b[5] + 65536*b[6] + 16777216*b[7], b[40] + 256*b[41] + 65536*b[42] + 16777216*b[43] }' } # fold # -> " " fold() { _r=$1; _c=$2; _nc=$3; _s=$4; _n=$5; shift 5 dd if="$_r" bs=$((2 * _nc)) skip="$_s" count="$_n" 2>/dev/null \ | od -An -v -td2 -w$((2 * _nc)) 2>/dev/null \ | awk -v c="$_c" -v plist="$*" ' BEGIN { np = split(plist, p, " ") } { v = $c; n++; s += v*v; for (i = 1; i <= np; i++) acc[i, (n-1) % p[i]] += v } END { if (!n || !s) { printf "0"; for (i = 1; i <= np; i++) printf " 0"; printf "\n"; exit } tot = s / n printf "%d", int(tot/1000 + 0.5) for (i = 1; i <= np; i++) { k = int(n / p[i]); fm = 0 for (j = 0; j < p[i]; j++) { m = acc[i, j] / k; fm += m*m } fm /= p[i] printf " %d", int(1000*fm/tot + 0.5) } printf "\n" }' } inrange() { # inrange if [ -n "$4" ] && [ "$4" -ge "$2" ] 2>/dev/null && [ "$4" -le "$3" ] 2>/dev/null then bst_ok "$1"; else bst_notok "$1" "$5 = ${4:-} (want $2..$3)"; fi } # ------------------------------------------------------------- the round trip --- # encode -> compare bitstream canonically -> decode the reference -> compare PCM # -> decode our own encode -> compare PCM again. The last of those is what makes # the encoder's output meaningful rather than merely equal to a stored blob. roundtrip() { # roundtrip [encflag] [maxdev] _n=$1; _ch=$2; _rate=$3; _wsz=$4; _dsz=$5; _flag=$6; _maxdev=${7:-1} _src=$BST_DATA/speex-$_n.wav _ref=$BST_DATA/speex-$_n.ref.spx _refw=$BST_DATA/speex-$_n.ref.wav # --- encode on target --- _e=$(speexenc --quiet $_flag "$_src" "$tmp/$_n.spx" 2>&1); _rc=$? check_eq speex/$_n-enc-rc 0 "$_rc" check_eq speex/$_n-enc-size "$(size "$_ref")" "$(size "$tmp/$_n.spx")" ogg_canon "$tmp/$_n.spx" "$tmp/$_n.mine.canon" "$tmp/$_n.mine.pages" ogg_canon "$_ref" "$tmp/$_n.ref.canon" "$tmp/$_n.ref.pages" check_eq speex/$_n-enc-pages "$(cat "$tmp/$_n.ref.pages" 2>/dev/null)" \ "$(cat "$tmp/$_n.mine.pages" 2>/dev/null)" if cmp -s "$tmp/$_n.mine.canon" "$tmp/$_n.ref.canon"; then bst_ok speex/$_n-enc-canonical echo "==SPEEX-AGREE $_n bitstream canonically identical to reference==" else _d=$(ndifflines "$tmp/$_n.mine.canon" "$tmp/$_n.ref.canon") bst_notok speex/$_n-enc-canonical "canonical bitstream differs from speex-$_n.ref.spx in $_d bytes" echo "==SPEEX-AGREE $_n bitstream ndiff=$_d==" fi # A second encode of the same input. Asserted CANONICALLY equal, never # raw-different -- see the header note on srand(time(NULL)). speexenc --quiet $_flag "$_src" "$tmp/$_n.again.spx" 2>/dev/null ogg_canon "$tmp/$_n.again.spx" "$tmp/$_n.again.canon" "$tmp/$_n.again.pages" check speex/$_n-enc-repeat-canon cmp -s "$tmp/$_n.mine.canon" "$tmp/$_n.again.canon" # --- decode the STORED reference: the decoder against a fixed input --- _e=$(speexdec --quiet "$_ref" "$tmp/$_n.wav" 2>&1); _rc=$? check_eq speex/$_n-dec-rc 0 "$_rc" check_eq speex/$_n-wav-size "$_wsz" "$(size "$tmp/$_n.wav")" set -- $(hdr "$tmp/$_n.wav") check_eq speex/$_n-riff "RIFF" "$1" check_eq speex/$_n-wave "WAVE" "$2" check_eq speex/$_n-channels "$_ch" "$3" check_eq speex/$_n-rate "$_rate" "$4" check_eq speex/$_n-bits "16" "$5" check_eq speex/$_n-data-size "$_dsz" "$7" if cmp -s "$tmp/$_n.wav" "$_refw"; then bst_ok speex/$_n-pcm-exact bst_ok speex/$_n-pcm-max-deviation bst_ok speex/$_n-pcm-exact-fraction else bst_notok speex/$_n-pcm-exact "decoded WAV differs from speex-$_n.ref.wav" tail -c +45 "$tmp/$_n.wav" > "$tmp/a.raw" 2>/dev/null tail -c +45 "$_refw" > "$tmp/b.raw" 2>/dev/null od -An -v -td2 -w2 "$tmp/a.raw" > "$tmp/a.txt" 2>/dev/null od -An -v -td2 -w2 "$tmp/b.raw" > "$tmp/b.txt" 2>/dev/null set -- $(awk 'NR==FNR{a[FNR]=$1;next} {d=a[FNR]-$1; if(d<0)d=-d; if(d>m)m=d; if(d)k++} END{print m+0, k+0, FNR}' "$tmp/a.txt" "$tmp/b.txt") echo "==SPEEX-AGREE $_n pcm max=${1:-?} ndiff=${2:-?} of ${3:-?}==" if [ -n "$1" ] && [ "$1" -le "$_maxdev" ] 2>/dev/null; then bst_ok speex/$_n-pcm-max-deviation else bst_notok speex/$_n-pcm-max-deviation "max |delta| ${1:-unknown} LSB > $_maxdev"; fi # Bounded as a FRACTION, not a count: the drift scales with the sample # count, so a fixed 64 was a bound on nothing. See the header note. if [ -n "$2" ] && [ -n "$3" ] && [ $(($2 * 100)) -le $(($3 * PCM_DRIFT_PCT)) ] 2>/dev/null; then bst_ok speex/$_n-pcm-exact-fraction else bst_notok speex/$_n-pcm-exact-fraction "${2:-unknown} of ${3:-?} samples differ, bound ${PCM_DRIFT_PCT}%" fi fi # --- decode OUR OWN encode: the half that makes the encoder's output mean # something. Byte-identical to the reference decode, which it can only be if # the Speex packets we produced are the packets x86-64 produced. speexdec --quiet "$tmp/$_n.spx" "$tmp/$_n.rt.wav" 2>/dev/null check speex/$_n-roundtrip cmp -s "$tmp/$_n.rt.wav" "$_refw" tail -c +45 "$tmp/$_n.wav" > "$tmp/$_n.raw" 2>/dev/null } # name ch rate wavsize datasize encflag roundtrip nb8k 1 8000 16124 16080 "" 1 roundtrip wb16k 1 16000 16204 16160 "" 1 roundtrip uwb32k 1 32000 16684 16640 "" 1 roundtrip st16k 2 16000 32364 32320 "" 2 roundtrip vbr8k 1 8000 16124 16080 "--vbr" 1 # ----------------------------------------------------------- the coder modes --- # What the encoder says it selected. This is the only place the three CELP modes # are named, and it is what separates "the file happened to match" from "the # wideband path ran". speexenc prints this on stderr without --quiet. mode() { # mode _m=$(speexenc "$BST_DATA/speex-$1.wav" "$tmp/mode.spx" 2>&1 >/dev/null) case "$_m" in *"$2"*) bst_ok speex/$1-mode ;; *) bst_notok speex/$1-mode "want '$2' got '$(_bst_1line "$_m")'" ;; esac } mode nb8k "narrowband mode (mono)" mode wb16k "wideband (sub-band CELP) mode (mono)" mode uwb32k "ultra-wideband (sub-band CELP) mode (mono)" mode st16k "wideband (sub-band CELP) mode (stereo)" # --------------------------------------------------------------- the signal ---- # Window: 6400 frames from frame 800, clear of the codec's priming. 6400 is a # multiple of every period used, which is what makes the fold exact. Periods: # 20 = 400 Hz at 8 kHz, 40 at 16 kHz, 80 at 32 kHz; 25 is the control. set -- $(fold "$tmp/nb8k.raw" 1 1 800 6400 20 25) inrange speex/nb8k-level 18500 22800 "$1" "window mean-square/1000" inrange speex/nb8k-fold400 700 800 "$2" "fold(20) ppt" inrange speex/nb8k-control 0 15 "$3" "fold(25) ppt" set -- $(fold "$tmp/wb16k.raw" 1 1 800 6400 40 25) inrange speex/wb16k-level 18500 22800 "$1" "window mean-square/1000" inrange speex/wb16k-fold400 700 800 "$2" "fold(40) ppt" inrange speex/wb16k-control 0 15 "$3" "fold(25) ppt" set -- $(fold "$tmp/uwb32k.raw" 1 1 800 6400 80 25) inrange speex/uwb32k-level 18500 22800 "$1" "window mean-square/1000" inrange speex/uwb32k-fold400 700 800 "$2" "fold(80) ppt" inrange speex/uwb32k-control 0 15 "$3" "fold(25) ppt" # The gated fixture: half the window is true silence, so the periodic fraction is # diluted rather than absent. Measured 484 ppt against 749 on the ungated signal. set -- $(fold "$tmp/vbr8k.raw" 1 1 800 6400 20 25) inrange speex/vbr8k-level 16700 20500 "$1" "window mean-square/1000" inrange speex/vbr8k-fold400 440 530 "$2" "fold(20) ppt" inrange speex/vbr8k-control 0 15 "$3" "fold(25) ppt" # ------------------------------------------------------------ intensity stereo -- # Both channels carry the same 400 Hz signal -- that is what the format encodes -- # and what is asserted is that the 2:1 amplitude difference survived as a 4:1 # power ratio, and that the channels are not simply duplicated. set -- $(fold "$tmp/st16k.raw" 1 2 800 6400 40 25) inrange speex/st16k-L-level 17000 20800 "$1" "L window mean-square/1000" inrange speex/st16k-L-fold400 700 800 "$2" "L fold(40) ppt" inrange speex/st16k-L-control 0 15 "$3" "L fold(25) ppt" set -- $(fold "$tmp/st16k.raw" 2 2 800 6400 40 25) inrange speex/st16k-R-level 3700 4700 "$1" "R window mean-square/1000" inrange speex/st16k-R-fold400 700 800 "$2" "R fold(40) ppt" inrange speex/st16k-R-control 0 15 "$3" "R fold(25) ppt" # The balance itself, as a ratio in parts per thousand. Measured 4417; a decoder # that dropped the balance and emitted the downmix twice would read 1000, and one # that lost a channel would read 0 or overflow. Both are far outside the band. set -- $(od -An -v -td2 -w4 "$tmp/st16k.raw" 2>/dev/null | awk ' { n++; sl += $1*$1; sr += $2*$2; if ($1 != $2) d++ } END { if (!n || !sr) { print 0, 0, 0; exit } printf "%d %d %d\n", int(1000*(sl/sr) + 0.5), d+0, n }') inrange speex/st16k-balance 3600 5400 "$1" "L/R power ratio x1000" inrange speex/st16k-channels-differ 7000 "$3" "$2" "frames with L != R (of $3)" # ------------------------------------------------------------------- VBR -------- # vbr.c is the only libm-heavy path in the encoder and it is dead unless --vbr is # passed, so these three are the whole of its coverage. # # WHAT IS ASSERTED IS NOT "the VBR stream is smaller". On the gated fixture it is # smaller by 10 bytes of 2184 -- the gate's silence still costs bits at quality 8 # -- and a case demanding a large reduction there would be pinning an accident of # the fixture. What separates VBR-on from VBR-off on that input is that the # BITSTREAM DIFFERS (measured 1948 of 2184 bytes), and what shows the rate really # adapts is TRUE silence, where the same encoder falls from 2184 to 552 bytes. # The silent input is built here from the fixture's own 44-byte header plus # zeroed data, so it needs no payload of its own and cannot drift from it. speexenc --quiet "$BST_DATA/speex-vbr8k.wav" "$tmp/cbr.spx" 2>/dev/null ogg_canon "$tmp/cbr.spx" "$tmp/cbr.canon" "$tmp/cbr.pages" ogg_canon "$tmp/vbr8k.spx" "$tmp/vbr.canon" "$tmp/vbr.pages" if cmp -s "$tmp/cbr.canon" "$tmp/vbr.canon"; then bst_notok speex/vbr-differs-from-cbr "--vbr produced the same bitstream as CBR: the flag did not take" else bst_ok speex/vbr-differs-from-cbr fi head -c 44 "$BST_DATA/speex-vbr8k.wav" > "$tmp/silence.wav" 2>/dev/null dd if=/dev/zero bs=1 count=16000 >> "$tmp/silence.wav" 2>/dev/null check_eq speex/vbr-silence-input-size 16044 "$(size "$tmp/silence.wav")" speexenc --quiet "$tmp/silence.wav" "$tmp/sil-cbr.spx" 2>/dev/null speexenc --quiet --vbr "$tmp/silence.wav" "$tmp/sil-vbr.spx" 2>/dev/null scbr=$(size "$tmp/sil-cbr.spx"); svbr=$(size "$tmp/sil-vbr.spx") echo "==SPEEX-VBR silence cbr=$scbr vbr=$svbr==" # Measured 2184 -> 552, a 3.96x reduction. The bound is 2x, which separates "the # rate adapts" from "the flag is accepted and ignored" without fitting the # measurement. if [ -n "$svbr" ] && [ -n "$scbr" ] && [ "$svbr" -gt 0 ] 2>/dev/null \ && [ "$((scbr / svbr))" -ge 2 ] 2>/dev/null; then bst_ok speex/vbr-silence-collapses else bst_notok speex/vbr-silence-collapses "silence: cbr=${scbr:-?} vbr=${svbr:-?}, want cbr >= 2x vbr" fi # ------------------------------------------------------- no audio device -------- # speexdec with NO output file plays to a soundcard. configure's # AC_CHECK_HEADERS(sys/soundcard.h) succeeds because glibc ships the header, so # that path is compiled in on an image that has no /dev/dsp and no device nodes # at all. It fails cleanly -- diagnostic on stderr, exit 1 -- rather than hanging # or writing the PCM to the terminal, and THAT is the property worth pinning: it # is upstream behaviour a reader meeting it for the first time would otherwise # report against this port. There is no configure switch to change it, unlike # a52dec's --disable-oss next door, because playing is speexdec's documented # default rather than a driver-table accident. e=$(speexdec "$BST_DATA/speex-nb8k.ref.spx" 2>&1 >/dev/null); rc=$? check_eq speex/dec-no-device-rc 1 "$rc" case "$e" in *"Cannot open /dev/dsp"*) bst_ok speex/dec-no-device-diagnostic ;; *) bst_notok speex/dec-no-device-diagnostic "$(_bst_1line "$e")" ;; esac # --------------------------------------------------------- malformed input ------ # A file that is not there is the ONE malformed case speexdec fails on. check_rc speex/enc-missing-input-rc 1 speexenc "$tmp/no-such-file.wav" "$tmp/x.spx" check_rc speex/dec-missing-input-rc 1 speexdec "$tmp/no-such-file.spx" "$tmp/x.wav" # EVERY OTHER MALFORMED INPUT EXITS 0, AND THAT IS PINNED RATHER THAN CORRECTED. # speexdec prints "This doesn't look like a Speex file", writes NO output file at # all, and returns SUCCESS -- so a caller that checks only the status believes it # has a decode and then finds nothing on disk. It is upstream behaviour, it is # identical on the native x86-64 build, and an unpinned wrong is indistinguishable # from a port defect the first time someone meets it. Both halves are asserted: # the status, because that is the surprising part, and the ABSENT output file, # because that is what a caller actually trips over. dd if=/dev/zero bs=1 count=4096 2>/dev/null | tr '\0' 'X' > "$tmp/junk.spx" e=$(speexdec "$tmp/junk.spx" "$tmp/junk.wav" 2>&1 >/dev/null); rc=$? check_eq speex/dec-junk-rc 0 "$rc" case "$e" in *"doesn't look like a Speex file"*) bst_ok speex/dec-junk-diagnostic ;; *) bst_notok speex/dec-junk-diagnostic "$(_bst_1line "$e")" ;; esac if [ -e "$tmp/junk.wav" ]; then bst_notok speex/dec-junk-no-output "speexdec wrote $tmp/junk.wav for a non-Speex input" else bst_ok speex/dec-junk-no-output; fi : > "$tmp/empty.spx" e=$(speexdec "$tmp/empty.spx" "$tmp/empty.wav" 2>&1 >/dev/null); rc=$? check_eq speex/dec-empty-rc 0 "$rc" if [ -e "$tmp/empty.wav" ]; then bst_notok speex/dec-empty-no-output "speexdec wrote $tmp/empty.wav for an empty input" else bst_ok speex/dec-empty-no-output; fi # A truncated stream. 1200 of the narrowband fixture's 2184 bytes carries both # Ogg header pages and part of the audio page, so the headers parse, the tool # exits 0, and it writes a 44-byte WAV WITH NO SAMPLES IN IT -- the same # believable-but-empty result as the junk case, reached a different way. The size # is exact, not a range: it is a header and nothing else. head -c 1200 "$BST_DATA/speex-nb8k.ref.spx" > "$tmp/trunc.spx" 2>/dev/null speexdec --quiet "$tmp/trunc.spx" "$tmp/trunc.wav" 2>/dev/null check_eq speex/dec-truncated-rc 0 "$?" check_eq speex/dec-truncated-size 44 "$(size "$tmp/trunc.wav")" # A WAV the encoder cannot use: 4096 bytes of 'X' has no RIFF header, so # speexenc treats it as RAW input at its default 8 kHz and encodes it. That is # upstream's documented fallback, not a defect, and it is pinned so a reader does # not report the absence of a refusal as one. speexenc --quiet "$tmp/junk.spx" "$tmp/rawin.spx" 2>/dev/null check_eq speex/enc-headerless-is-raw-rc 0 "$?" if [ -s "$tmp/rawin.spx" ]; then bst_ok speex/enc-headerless-produces-stream else bst_notok speex/enc-headerless-produces-stream "empty output from headerless input"; fi rm -rf "$tmp"