. "$BST_LIB" # BST-TIMEOUT: 300 # measured 37s over 98 cases; ~22 execs of a 3.9 MB binary, # # each a fresh wasm module instantiation. 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 under 2x on a loaded host. # faad2 2.11.1+git (meta-oe recipes-multimedia/faad2, SRCREV 216f00e8ddba) -- # the FAAD2 MPEG-2/MPEG-4 AAC decoder: libfaad.a, libfaad_drm.a, two headers, a # pkg-config file, and the `faad` command-line decoder. `faad -o out.wav in.aac` # writes a RIFF/WAVE file and drives no sound card, so the whole of this suite # runs offline with no audio device, which is the ticket's own framing. # # THE ORACLE IS A STORED REFERENCE DECODE, AND UNLIKE ITS NEIGHBOURS THAT IS # LEGITIMATE HERE. 57-libvorbis.test and 57-speexdsp.test both refuse a stored # cross-machine digest, and 57-mpg123.test can only use one after neutralising # -ffast-math; all three are right about their own subject. faad2 is a different # case and the difference was MEASURED rather than assumed: the same source # built with gcc 9.5, with clang 10, and with gcc -O0 produces BYTE-IDENTICAL # decoded PCM for all three fixtures. It has no -ffast-math (its only # compiler-conditional flag is -ffloat-store, gcc-only, and dropping it changes # nothing), wasm32 has no FMA to contract into, and in the configuration these # fixtures use -- no PNS, no intensity stereo, no DRC -- the only libm on the # decode path is the cos/sin that fills the FFT twiddle table in cfft.c, whose # double results are cast to float and so absorb a last-ulp disagreement. # # So each fixture ships its own reference decode and the gate is `cmp`. When cmp # says equal, max|delta| is 0 and the differing-sample count is 0 BY # CONSTRUCTION, and the two graded cases below say so without paying for an # od/awk pass over 25k samples. When it says unequal they run for real and bound # the damage: <= 1 LSB, and <= 64 samples. A red on the graded pair is an # arithmetic fault; a red on cmp alone with the graded pair green is last-bit # drift in the wasm32 libm and should be re-referenced, not chased. # # WHAT THE FIXTURES ARE, AND WHAT EACH ONE IS FOR. All three are synthetic tones # built by src/faad2_mkfixtures.py and encoded with faac 1.31.1 at -q 500 # --joint 0 --pns 0 --no-tns; that file states why none of those is a default. # # faad2-tone44k mono 44100 Hz 441 Hz @ 9000 + 2205 Hz @ 3000 # faad2-stereo44k stereo 44100 Hz L 441 Hz @ 9000 / R 882 Hz @ 3000 # faad2-tone8k mono 8000 Hz 500 Hz @ 9000 # # TWO OF THE THREE EXIST TO PIN A DECODER BEHAVIOUR THAT THE STREAM DOES NOT # ANNOUNCE. libfaad/decoder.c's NeAACDecInit rewrites the output geometry for # implicit signalling before a sample is decoded: # # * every MONO stream is up-matrixed to TWO channels (implicit Parametric # Stereo), so faad2-tone44k -- a mono file -- decodes to a stereo WAV whose # two channels are bit-identical. faad2/tone44k-mono-upmatrix asserts that # identity exactly, which is a stronger statement than any tolerance and # needs no reference at all; # * every stream at <= 24000 Hz has its output sample rate DOUBLED (implicit # SBR), so faad2-tone8k -- an 8000 Hz file -- decodes at 16000 Hz, and # `faad -i` reports 16000 Hz for a stream whose ADTS header says 8000. # # Both are upstream's, both are unconditional in this build, and neither is # obvious from the CLI. A reader who does not know them reads the 16000 in the # report as a bug in the port. # # THE SIGNAL CASES USE AN INTEGER PERIOD-FOLD AND NO TRIG AT ALL. Every tone # frequency divides its OUTPUT sample rate exactly (441->100 samples, 882->50, # 2205->20, 500 at the doubled 16000 Hz -> 32), so folding an 8000-sample window # into P bins and taking the mean square of the bin averages isolates everything # periodic at P and averages the rest toward zero. Reported as parts-per- # thousand of the window's own mean square, it is a ratio of integer sums: no # sqrt, no sin, no cos. That is deliberate. 57-speexdsp.test found awk's math # support silently absent under busybox awk, where sqrt/sin/cos yield an EMPTY # number and take the whole expression with them -- a red carrying a blank where # the measurement should be, indistinguishable from a port defect. Nothing here # can produce one. # # 125 IS THE NEGATIVE CONTROL AND IT IS NOT ARBITRARY. Folding at 125 samples # keeps only multiples of 352.8 Hz (44100/125) or 128 Hz (16000/125), and no # tone in any fixture is one, so a decode that turned into broadband noise -- # which is exactly what the first, badly-encoded version of these fixtures did # produce -- lifts fold(125) off the floor while every positive case could still # look plausible. # # READING A RED. A wrong *-data-size, *-frames or *-rate is a decoder or # packaging fault and nothing else; those are integer and cannot drift. A red on # faad2/*-pcm-exact alone is discussed above. A red on the fold cases with the # byte comparisons green is impossible and means the suite itself is broken. require faad faad2/present tmp=${TMPDIR:-/tmp}/bst-faad2.$$ 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_rc faad2/usage-rc 1 faad -h h=$(faad -h 2>&1) case "$h" in *"MPEG-4 AAC Decoder V2.11.1"*) bst_ok faad2/version ;; *) bst_notok faad2/version "$(_bst_1line "$h")" ;; esac # The recipe builds the float library, not the FIXED_POINT one; every number in # this file is a claim about that decoder and not the other. case "$h" in *"Floating point version"*) bst_ok faad2/float-build ;; *) bst_notok faad2/float-build "$(_bst_1line "$h")" ;; esac check faad2/lib-static test -f /usr/lib/libfaad.a check faad2/lib-drm-static test -f /usr/lib/libfaad_drm.a check faad2/header-neaacdec test -f /usr/include/neaacdec.h check faad2/header-faad test -f /usr/include/faad.h check faad2/pkgconfig test -f /usr/lib/pkgconfig/faad2.pc check_out faad2/pkgconfig-version "Version: 2.11.1" cat /usr/lib/pkgconfig/faad2.pc # faad_fixed and faad_drm_fixed are BUILT by upstream's CMakeLists and are not # in its install list, so their absence is upstream's packaging and not a # truncated install; asserted so a future recipe that starts shipping them is # noticed rather than assumed. check faad2/no-fixed-lib test ! -f /usr/lib/libfaad_fixed.a so=$(ls /usr/lib/libfaad*.so* 2>/dev/null) if [ -z "$so" ]; then bst_ok faad2/static-only else bst_notok faad2/static-only "$(_bst_1line "$so")"; fi # ---------------------------------------------------------------- payloads --- # Pinned BEFORE anything is decoded. Without this a later mismatch has two # indistinguishable causes -- the decoder 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 faad2/fixture-tone44k-digest 58e7b22add6da383376e8c108c69ce96 "$BST_DATA/faad2-tone44k.aac" pin faad2/fixture-stereo44k-digest 961ac0cc2215ed382bcfb6069bff5a44 "$BST_DATA/faad2-stereo44k.aac" pin faad2/fixture-tone8k-digest 466c29cfb68396734cdb75b651adc423 "$BST_DATA/faad2-tone8k.aac" pin faad2/reference-tone44k-digest 806d2cbf50341af1e08e650c85b9449f "$BST_DATA/faad2-tone44k.ref.pcm" pin faad2/reference-stereo44k-digest ed2f17ba589e33d5886af6783ce14f81 "$BST_DATA/faad2-stereo44k.ref.pcm" pin faad2/reference-tone8k-digest 4d05197cddbb062ee0d30c372029f9a1 "$BST_DATA/faad2-tone8k.ref.pcm" for f in faad2-tone44k faad2-stereo44k faad2-tone8k; do if [ ! -f "$BST_DATA/$f.aac" ] || [ ! -f "$BST_DATA/$f.ref.pcm" ]; then bst_notok faad2/payloads "missing $BST_DATA/$f.aac or its reference -- remaining cases not run" rm -rf "$tmp"; exit 0 fi done # ------------------------------------------------------- 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. Fields: channels @22 (u16le), sample rate @24 # (u32le), bits @34 (u16le), data size @40 (u32le). hdr() { # hdr -> "magic wavemagic channels rate bits datasize" od -An -v -tu1 -N 44 "$1" 2>/dev/null | awk ' { for (i = 1; i <= NF; i++) b[n++] = $i } END { magic = sprintf("%c%c%c%c", b[0], b[1], b[2], b[3]) wave = sprintf("%c%c%c%c", b[8], b[9], b[10], b[11]) ch = b[22] + 256*b[23] rate = b[24] + 256*b[25] + 65536*b[26] + 16777216*b[27] bits = b[34] + 256*b[35] dsz = b[40] + 256*b[41] + 65536*b[42] + 16777216*b[43] printf "%s %s %d %d %d %d\n", magic, wave, ch, rate, bits, dsz }' } # 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 + 1))" -v plist="$*" ' { v = $c; n++; s += v*v; for (i = 1; i <= np; i++) acc[i, (n-1) % p[i]] += v } BEGIN { np = split(plist, p, " ") } 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 decodes --- # decode decode() { _n=$1; _rate=$2; _dsz=$3; _fr=$4; _info=$5 _f=faad2-$_n _src=$BST_DATA/$_f.aac _wav=$tmp/$_f.wav _raw=$tmp/$_f.raw check faad2/$_n-decode faad -q -o "$_wav" "$_src" check faad2/$_n-decode-raw faad -q -f 2 -o "$_raw" "$_src" check_eq faad2/$_n-wav-size "$((_dsz + 44))" "$(size "$_wav")" set -- $(hdr "$_wav") check_eq faad2/$_n-riff "RIFF" "$1" check_eq faad2/$_n-wave "WAVE" "$2" check_eq faad2/$_n-channels "2" "$3" check_eq faad2/$_n-rate "$_rate" "$4" check_eq faad2/$_n-bits "16" "$5" check_eq faad2/$_n-data-size "$_dsz" "$6" check_eq faad2/$_n-frames "$_fr" "$(( ${6:-0} / 4 ))" # what the decoder reports on the way -- the ticket's own criterion, and for # tone8k the doubled rate rather than the stream's own. _i=$(faad -i "$_src" 2>&1) case "$_i" in *"$_info"*) bst_ok faad2/$_n-info ;; *) bst_notok faad2/$_n-info "want '$_info' got '$(_bst_1line "$_i")'" ;; esac # -f 2 and the WAV's data chunk are the same bytes by different routes. tail -c +45 "$_wav" > "$tmp/payload.pcm" 2>/dev/null check faad2/$_n-raw-matches-wav cmp -s "$_raw" "$tmp/payload.pcm" # the reference decode _ref=$BST_DATA/$_f.ref.pcm check_eq faad2/$_n-ref-size "$_dsz" "$(size "$_ref")" if cmp -s "$_raw" "$_ref"; then bst_ok faad2/$_n-pcm-exact bst_ok faad2/$_n-pcm-max-deviation bst_ok faad2/$_n-pcm-exact-fraction echo "==FAAD2-AGREE $_n byte-identical to reference==" else bst_notok faad2/$_n-pcm-exact "decoded PCM differs from $_f.ref.pcm" od -An -v -td2 -w2 "$_raw" > "$tmp/a.txt" 2>/dev/null od -An -v -td2 -w2 "$_ref" > "$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)n++} END{print m+0, n+0, FNR}' "$tmp/a.txt" "$tmp/b.txt") echo "==FAAD2-AGREE $_n max=${1:-?} ndiff=${2:-?} of ${3:-?}==" if [ -n "$1" ] && [ "$1" -le 1 ] 2>/dev/null; then bst_ok faad2/$_n-pcm-max-deviation else bst_notok faad2/$_n-pcm-max-deviation "max |delta| ${1:-unknown} LSB > 1"; fi if [ -n "$2" ] && [ "$2" -le 64 ] 2>/dev/null; then bst_ok faad2/$_n-pcm-exact-fraction else bst_notok faad2/$_n-pcm-exact-fraction "${2:-unknown} of ${3:-?} samples differ, bound 64"; fi fi } decode tone44k 44100 49152 12288 "ADTS, 0.279 sec, 35 kbps, 44100 Hz" decode stereo44k 44100 49152 12288 "ADTS, 0.279 sec, 71 kbps, 44100 Hz" decode tone8k 16000 57344 14336 "ADTS, 0.896 sec, 5 kbps, 16000 Hz" # ------------------------------------------------- the two implicit rewrites --- # A mono stream decoded to two BIT-IDENTICAL channels. Exact, no tolerance. tail -c +45 "$tmp/faad2-tone44k.wav" > "$tmp/m.pcm" 2>/dev/null od -An -v -td2 -w4 "$tmp/m.pcm" 2>/dev/null | awk '$1!=$2{n++} END{print n+0}' > "$tmp/m.n" check_eq faad2/tone44k-mono-upmatrix 0 "$(cat "$tmp/m.n" 2>/dev/null)" # ...and a true stereo stream that does NOT collapse that way. od -An -v -td2 -w4 "$tmp/faad2-stereo44k.raw" 2>/dev/null | awk '$1!=$2{n++} END{print n+0}' > "$tmp/s.n" sdiff=$(cat "$tmp/s.n" 2>/dev/null) # Measured 9337 of 12288 -- not 12288, because two sines cross zero together and # the priming and tail are silent in both channels. The bound is what separates # "two different signals" from the mono up-matrix's exact 0, not a tight fit. if [ -n "$sdiff" ] && [ "$sdiff" -ge 5000 ] 2>/dev/null; then bst_ok faad2/stereo44k-channels-differ else bst_notok faad2/stereo44k-channels-differ "only ${sdiff:-} of 12288 frames have L != R (want >= 5000)"; fi # ------------------------------------------------------------ the signal ------ # Window: 8000 frames starting at frame 2000. 8000 is a multiple of every period # used below, which is what makes the fold exact rather than approximate. set -- $(fold "$tmp/faad2-tone44k.raw" 0 2 2000 8000 100 20 50 125) inrange faad2/tone44k-level 40000 50000 "$1" "window mean-square/1000" inrange faad2/tone44k-fold441 950 1000 "$2" "fold(100) ppt" inrange faad2/tone44k-fold2205 70 140 "$3" "fold(20) ppt" inrange faad2/tone44k-fold882-absent 0 20 "$4" "fold(50) ppt" inrange faad2/tone44k-control 0 20 "$5" "fold(125) ppt" set -- $(fold "$tmp/faad2-stereo44k.raw" 0 2 2000 8000 100 50 125) inrange faad2/stereo44k-L-level 36000 45000 "$1" "L window mean-square/1000" inrange faad2/stereo44k-L-fold441 950 1000 "$2" "L fold(100) ppt" inrange faad2/stereo44k-L-fold882-absent 0 20 "$3" "L fold(50) ppt" inrange faad2/stereo44k-L-control 0 20 "$4" "L fold(125) ppt" set -- $(fold "$tmp/faad2-stereo44k.raw" 1 2 2000 8000 50 20 125) inrange faad2/stereo44k-R-level 4000 5000 "$1" "R window mean-square/1000" inrange faad2/stereo44k-R-fold882 950 1000 "$2" "R fold(50) ppt" inrange faad2/stereo44k-R-fold2205-absent 0 20 "$3" "R fold(20) ppt" inrange faad2/stereo44k-R-control 0 20 "$4" "R fold(125) ppt" set -- $(fold "$tmp/faad2-tone8k.raw" 0 2 2000 8000 32 16 125) inrange faad2/tone8k-level 31000 39000 "$1" "window mean-square/1000" inrange faad2/tone8k-fold500 800 950 "$2" "fold(32) ppt" inrange faad2/tone8k-fold1000-absent 0 20 "$3" "fold(16) ppt" inrange faad2/tone8k-control 0 20 "$4" "fold(125) ppt" # ------------------------------------------------------------ determinism ------ # What makes a stored reference an oracle at all, so it is asserted, not assumed. faad -q -o "$tmp/again.wav" "$BST_DATA/faad2-stereo44k.aac" 2>/dev/null check faad2/determinism cmp -s "$tmp/faad2-stereo44k.wav" "$tmp/again.wav" # ------------------------------------------------- the channel-position table --- d=$(faad -o "$tmp/t.wav" "$BST_DATA/faad2-stereo44k.aac" 2>&1) case "$d" in *"Config: 2 Ch"*) bst_ok faad2/channel-config ;; *) bst_notok faad2/channel-config "$(_bst_1line "$d")" ;; esac case "$d" in *"Left front"*) bst_ok faad2/channel-left ;; *) bst_notok faad2/channel-left "$(_bst_1line "$d")" ;; esac case "$d" in *"Right front"*) bst_ok faad2/channel-right ;; *) bst_notok faad2/channel-right "$(_bst_1line "$d")" ;; esac # --------------------------------------------------------------- -w to stdout --- # faad -w writes the same 44-byte header and the same samples, but it CANNOT # seek back to patch the two length fields, and upstream leaves them at zero # rather than at the streaming convention of 0xFFFFFFFF. The body is identical # and the header is not, and both halves are pinned: the first is the property # anyone piping faad depends on, the second is an upstream wart that a reader # comparing the two files would otherwise report as a port defect. faad -q -w "$BST_DATA/faad2-stereo44k.aac" > "$tmp/w.wav" 2>/dev/null check_eq faad2/stdout-size "$(size "$tmp/faad2-stereo44k.wav")" "$(size "$tmp/w.wav")" tail -c +45 "$tmp/w.wav" > "$tmp/w.pcm" 2>/dev/null check faad2/stdout-body cmp -s "$tmp/w.pcm" "$tmp/faad2-stereo44k.raw" set -- $(hdr "$tmp/w.wav") check_eq faad2/stdout-header-unpatched 0 "$6" # ------------------------------------------------------------- malformed input --- # Each of these pins CURRENT upstream behaviour. Two of them are wrong in # upstream's own terms and are pinned anyway, because an unpinned wrong is # indistinguishable from a port defect the first time someone meets it. src=$BST_DATA/faad2-stereo44k.aac head -c 823 "$src" > "$tmp/trunc.aac" 2>/dev/null check_eq faad2/truncated-input-size 823 "$(size "$tmp/trunc.aac")" # A truncated stream decodes as far as it can and exits 0 -- no diagnostic, no # non-zero status. The output length is deterministic, so it is what is asserted. faad -q -o "$tmp/trunc.wav" "$tmp/trunc.aac" 2>/dev/null; rc=$? check_eq faad2/truncated-rc 0 "$rc" check_eq faad2/truncated-size 12332 "$(size "$tmp/trunc.wav")" # Data that is not AAC at all: `faad -i` calls it "RAW", the decode writes NO # output file, and it STILL exits 0. That last part is upstream's bug -- a # caller checking only the status believes it has a WAV. printf 'this is not an aac stream\n%.0s' 1 2 3 4 5 6 7 8 9 10 > "$tmp/junk.aac" rm -f "$tmp/junk.wav" faad -q -o "$tmp/junk.wav" "$tmp/junk.aac" 2>/dev/null; rc=$? check_eq faad2/junk-rc 0 "$rc" check faad2/junk-no-output test ! -f "$tmp/junk.wav" : > "$tmp/empty.aac" check_rc faad2/empty-rc 1 faad -q -o "$tmp/empty.wav" "$tmp/empty.aac" check_rc faad2/missing-input-rc 1 faad -i "$tmp/no-such-file.aac" rm -rf "$tmp"