. "$BST_LIB" # wavpack 5.6.0 (meta-oe recipes-multimedia/wavpack) -- the WavPack compressor, # its wvunpack decompressor, and the wvgain/wvtag utilities packaged with them. # # WHAT THIS FILE ASSERTS, AND WHY IT CAN ASSERT MORE THAN THE REST OF THE AUDIO # TIER. A media package on a headless browser guest is normally testable only to # the "it ran and printed a banner" standard -- no sound card, no ALSA, no # /dev/dsp, nothing to listen with. flac escapes that because LOSSLESSNESS IS AN # EXACT OFFLINE PROPERTY, and the gate below is the ticket's own criterion in # exactly that shape: encode a generated PCM file, decode it again, `cmp`. What # wavpack adds -- and the reason this suite is not just 57-flac with the nouns # changed -- is that ITS LOSSY PATH IS EXACTLY CHECKABLE TOO. In hybrid mode the # encoder writes a small lossy .wv plus a .wvc correction file, and decoding with # both present is bit-for-bit the original again while decoding the .wv alone # (wvunpack -i) is bit-for-bit NOT. So the same lossy encode yields one hard # identity and one hard non-identity, both offline and device-free, where libopus # had to reach for a digest and a spectral-distance threshold to say anything at # all about a lossy codec. # # THE FORMAT ALSO CARRIES ITS OWN ORACLE, like flac's STREAMINFO and for the same # reason it is worth using: `wavpack -m` stores an MD5 of the RAW SAMPLES computed # by the encoder, and `wvunpack -m -v` recomputes it from the decoded output and # compares. Because the fixture here is signed 16-bit little-endian mono -- the # byte order that digest is defined over -- the stored value must ALSO equal # md5sum of the fixture file itself. That ties wavpack's internal accounting to a # digest computed by an entirely different program, which is a stronger statement # than either check alone: an encoder that was CONSISTENTLY wrong would still # pass its own verify. On this image that digest comes from openssl's libcrypto # rather than wavpack's bundled MD5 (the recipe DEPENDS on openssl and # WAVPACK_ENABLE_LIBCRYPTO follows), so the cross-check spans two ported packages. # # AND ONE CASE ASSERTS THE VERIFIER IS NOT VACUOUS. A `wvunpack -v` that returned # 0 unconditionally would satisfy every other case here. So one byte of the # compressed stream is flipped and the verify is required to FAIL -- otherwise # "verify passed" carries no information. # # WHAT A GREEN HERE SAYS ABOUT wasm32. WAVPACK_ENABLE_ASM is a dependent option on # HAVE_ASM, which needs CMAKE_ASM-ATT_COMPILER and an x86/arm32 CMAKE_SYSTEM_PROCESSOR # -- wasm32 is neither, so no assembly is compiled and what runs on target is the # portable C decorrelation and entropy-coding path. A lossless round trip through # it is therefore a statement about this arm's integer arithmetic and shifts. # # THE FIXTURE IS BUILT WITHOUT FORKING. Every byte of the 32 KiB sawtooth is # emitted by shell arithmetic and the printf builtin, not by a `printf '%03o'` # subshell per byte. On an arm where each exec is a fresh wasm module # instantiation and guest memory is not returned on process exit, that is the # difference between a fixture and a cost. require wavpack wavpack/present tmp=${TMPDIR:-/tmp}/bst-wavpack.$$ mkdir -p "$tmp" 2>/dev/null pcm=$tmp/tone.pcm noise=$tmp/noise.pcm wv=$tmp/tone.wv back=$tmp/back.raw hyb=$tmp/hyb.wv hback=$tmp/hyb-corrected.raw lback=$tmp/hyb-lossy.raw bad=$tmp/corrupt.wv gain=$tmp/gain.wv wav=$tmp/tone.wav wwv=$tmp/tone-wav.wv wback=$tmp/back.wav # ------------------------------------------------------------------ fixture --- # A 256-sample sawtooth, 16-bit signed little-endian mono, repeated 64 times: # 16384 samples / 32768 bytes / 2.048 s at 8 kHz. Sawtooth rather than noise # because the codec must be seen to COMPRESS, and rather than silence because a # constant signal would exercise no decorrelation at all. The low byte is held at # zero so the waveform is a clean ramp in the high byte. \NNN escapes are # assembled by arithmetic expansion, a shell builtin. i=0; esc="" while [ $i -lt 256 ]; do esc="$esc\\000\\$((i/64))$((i/8%8))$((i%8))" i=$((i+1)) done j=0 while [ $j -lt 64 ]; do printf "$esc"; j=$((j+1)); done > "$pcm" nbytes=$(wc -c < "$pcm" 2>/dev/null | tr -d ' ') check_eq wavpack/fixture-size 32768 "$nbytes" # A fixture the shell failed to write would make every case below fail for a # reason that has nothing to do with wavpack, so stop here rather than emit # thirty misleading reds against the wrong subject. if [ "$nbytes" != "32768" ]; then bst_notok wavpack/fixture "the shell could not emit the 32768-byte PCM fixture -- remaining cases not run" rm -rf "$tmp" exit 0 fi RAWIN="--raw-pcm=8000,16,1,le" check_out wavpack/version "wavpack 5.6.0" wavpack --version check_out wvunpack/version "wvunpack 5.6.0" wvunpack --version # ------------------------------------------------ the ticket's own criterion --- # -m stores the MD5 of the raw samples, which is what makes the verify and the # cross-check below possible; -y overwrites without prompting and -q keeps the # per-file progress line off the report. check wavpack/encode wavpack -q -y -m $RAWIN "$pcm" -o "$wv" check_eq wavpack/magic "wvpk" "$(dd if="$wv" bs=1 count=4 2>/dev/null)" # It must actually be a codec, not a container rewrite. encbytes=$(wc -c < "$wv" 2>/dev/null | tr -d ' ') if [ -n "$encbytes" ] && [ "$encbytes" -lt 32768 ]; then bst_ok wavpack/compresses else bst_notok wavpack/compresses "encoded $encbytes bytes from a 32768-byte fixture -- no compression" fi # wvunpack -v decodes the whole stream and checks it against the stored MD5 # without writing an output file: the encoder's own accounting, re-run by the # decoder. check wvunpack/verify wvunpack -q -v "$wv" # The same digest, against one computed over the same bytes by a completely # different program. `-m -v` prints "original md5: " on stderr. if command -v md5sum >/dev/null 2>&1; then check_out wavpack/stored-md5 \ "original md5: $(md5sum < "$pcm" 2>/dev/null | cut -d' ' -f1)" \ wvunpack -m -v "$wv" else bst_skip wavpack/stored-md5 "md5sum not installed on this image" fi check wvunpack/decode wvunpack -q -y -r "$wv" -o "$back" # THE GATE: lossless means byte for byte, and this is the case the ticket names. check wavpack/lossless cmp "$pcm" "$back" # ------------------------------------------------------ the verifier is real --- # One byte of the compressed audio flipped to 0xFF. A verify that still passes # would mean every `wvunpack -v` above proved nothing. cp "$wv" "$bad" 2>/dev/null printf '\377' | dd of="$bad" bs=1 seek=200 count=1 conv=notrunc 2>/dev/null if cmp -s "$wv" "$bad"; then bst_notok wvunpack/detects-corruption "the corruption could not be written -- case not exercised" elif wvunpack -q -v "$bad" >/dev/null 2>&1; then bst_notok wvunpack/detects-corruption "a stream with a flipped byte verified clean -- every other wvunpack -v here proves nothing" else # Non-zero rather than a pinned rc: wvunpack distinguishes soft from hard # errors internally and which one a flipped byte produces is its business, # not this case's. bst_ok wvunpack/detects-corruption fi # ----------------------------------------------------------- the hybrid path --- # THE HYBRID CASES GET THEIR OWN FIXTURE, AND THAT IS THE INTERESTING PART. Run # against the sawtooth, this whole block asserts nothing: the ramp codes # losslessly at about 1.7 bits/sample, and wavpack recognises that a hybrid # target above what lossless already costs is not worth quantising for -- it # emits the plain lossless stream and a 42-byte stub .wvc. Measured on the # sawtooth, -b4 and every rate above it come back BYTE-IDENTICAL from the .wv # alone, so "the correction file is load-bearing" would have been a green case # proving the opposite of what it claims. Only -b2 is genuinely lossy there, and # -b2 is the option's own minimum (wavpack rejects below 2.0), which is no place # to put an assertion: one compression improvement upstream and it flips. # # So the hybrid block uses an INCOMPRESSIBLE fixture instead. At 16-bit noise the # lossless encode is 33422 bytes -- larger than the 32768 it started from -- so # every hybrid rate is far below what lossless needs and the lossy path is real # with a wide margin: -b8 splits it 15978 bytes of .wv against 17528 of .wvc. # The threshold is a property of the signal, not of the codec, which is exactly # why the fixture and not the bitrate is what got changed. # # 32-bit-safe LCG: the largest intermediate is 32748*1103+12345 = 36,138,589, so # the sequence does not depend on whether the target shell does 64-bit # arithmetic. 1024 samples emitted through the printf builtin, repeated 16 times # -- repetition costs nothing in entropy here because wavpack's model is a linear # predictor and an entropy coder, with no long-range matcher to notice it. s=1; i=0; esc="" while [ $i -lt 1024 ]; do s=$(( (s * 1103 + 12345) % 32749 )) lo=$(( s % 256 )); hi=$(( s / 127 % 256 )) esc="$esc\\$((lo/64))$((lo/8%8))$((lo%8))\\$((hi/64))$((hi/8%8))$((hi%8))" i=$((i+1)) done j=0 while [ $j -lt 16 ]; do printf "$esc"; j=$((j+1)); done > "$noise" check_eq wavpack/noise-fixture-size 32768 "$(wc -c < "$noise" 2>/dev/null | tr -d ' ')" # -b8 sets an 8 bits/sample lossy target (wavpack reads -bn below 24 as bits per # sample, above it as kbits/sec) and -c writes the correction file beside it, at # the output name with its extension replaced by .wvc. This is the mode no other # codec in this image has: the SAME encode is lossy or lossless depending only on # whether the correction file is read. check wavpack/hybrid-encode wavpack -q -y -m -b8 -c $RAWIN "$noise" -o "$hyb" if [ -s "$tmp/hyb.wvc" ]; then bst_ok wavpack/hybrid-correction-file else bst_notok wavpack/hybrid-correction-file "no .wvc written beside $hyb -- the hybrid cases below cannot mean anything" fi check wavpack/hybrid-decode wvunpack -q -y -r "$hyb" -o "$hback" # With the .wvc present the lossy encode is restored EXACTLY. check wavpack/hybrid-lossless cmp "$noise" "$hback" # -i ignores the correction file, which forces the lossy decode of the same .wv. check wavpack/hybrid-lossy-decode wvunpack -q -y -i -r "$hyb" -o "$lback" # Same number of samples out as in -- a length drift here would make the # difference below read like lossy coding when it was a framing fault. check_eq wavpack/hybrid-lossy-length 32768 "$(wc -c < "$lback" 2>/dev/null | tr -d ' ')" # ...and it must genuinely differ, or the correction file was not doing anything # and hybrid-lossless above was asserting nothing. check_rc wavpack/hybrid-lossy-differs 1 cmp "$noise" "$lback" # ------------------------------------------------------- compression levels --- # -f and -hh are the two extremes of the decorrelation search, not two settings # of one thing: -f is the fast fixed pass and -hh the very-high-quality one. # Both must be lossless, and both are arithmetic this arm has never run before. # Which is SMALLER is deliberately not asserted -- on a synthetic sawtooth that # would be a property of the fixture, not of the codec. for L in f hh; do lwv=$tmp/l$L.wv; lraw=$tmp/l$L.raw if wavpack -q -y -$L $RAWIN "$pcm" -o "$lwv" 2>/dev/null && wvunpack -q -y -r "$lwv" -o "$lraw" 2>/dev/null; then check "wavpack/mode-$L-lossless" cmp "$pcm" "$lraw" else bst_notok "wavpack/mode-$L-lossless" "encode or decode failed at -$L" fi done # ------------------------------------------------------------- the WAV path --- # The raw round trip above is the exact criterion, but nobody feeds wavpack raw # PCM in practice -- they feed it a WAV. The same samples in a canonical 44-byte # RIFF/WAVE wrapper, round-tripped through the container parser and writer: the # decoded file must equal the input byte for byte, HEADER INCLUDED, because # wavpack stores the original header and wvunpack puts it back. ob() { printf "\\$(($1%256/64))$(($1%256/8%8))$(($1%8))"; } le16() { ob $1; ob $(($1/256)); } le32() { ob $1; ob $(($1/256)); ob $(($1/65536)); ob $(($1/16777216)); } { printf 'RIFF'; le32 32804; printf 'WAVEfmt '; le32 16; le16 1; le16 1; le32 8000; le32 16000; le16 2; le16 16; printf 'data'; le32 32768; cat "$pcm"; } > "$wav" check_eq wavpack/wav-fixture-size 32812 "$(wc -c < "$wav" 2>/dev/null | tr -d ' ')" check wavpack/wav-encode wavpack -q -y -m "$wav" -o "$wwv" check wavpack/wav-decode wvunpack -q -y "$wwv" -o "$wback" check wavpack/wav-lossless cmp "$wav" "$wback" # ------------------------------------------------------------ wvgain, wvtag --- # Both ship in wavpack-bin with wavpack and wvunpack (lib_package's # FILES:${PN}-bin = "${bindir}/*"), so an absent one is a packaging fault on this # image rather than a legitimately absent tool -- which is why these are `not ok` # and not the `require` skip that guards wavpack itself. if [ ! -x /usr/bin/wvgain ]; then bst_notok wvgain/present "wvgain is not in /usr/bin, though it is packaged with wavpack" elif [ ! -x /usr/bin/wvtag ]; then bst_notok wvtag/present "wvtag is not in /usr/bin, though it is packaged with wavpack" else bst_ok wvgain/present # wvgain analyses the decoded audio and WRITES the result back as APEv2 tags; # wvtag then reads them. Two tools, one file, and a value neither could # fabricate on its own: 8 kHz is in wvgain's filter table, so the scan is real # analysis rather than a rejected sample rate. cp "$wv" "$gain" 2>/dev/null check wvgain/scan wvgain -q "$gain" check_out wvtag/replaygain-readback "dB" wvtag -q -x "replaygain_track_gain" "$gain" # A tag write of our own, on the untouched file: APEv2 append and read-back. check wvtag/write wvtag -q -w "Title=brintOS" "$wv" check_out wvtag/read "brintOS" wvtag -q -x "Title" "$wv" # The rewritten file must still verify: a tag append that corrupted a block # would otherwise pass both cases above. check wvtag/verify-after-write wvunpack -q -v "$wv" fi # ------------------------------------------------------------- static-only --- # No libwavpack.so on a distro that configures --disable-shared image-wide and # passes -DBUILD_SHARED_LIBS=OFF here. One appearing would mean the static-only # rule silently stopped holding -- and with it the reason EMCRAFT_TOOLS names # wavpack-bin rather than the bare `wavpack` lib_package would put the shared # object in. if [ -e /usr/lib/libwavpack.so ] || [ -e /usr/lib/libwavpack.so.1 ]; then bst_notok wavpack/static-only "a shared libwavpack shipped on a --disable-shared distro" else bst_ok wavpack/static-only fi # The library half of the deliverable. This image has no compiler, so these # cannot be linked against here -- their presence is what a downstream port # (and the SDK) needs, and their absence would mean the -dev/-staticdev split # silently stopped matching. missing="" for f in /usr/lib/libwavpack.a /usr/include/wavpack/wavpack.h /usr/lib/pkgconfig/wavpack.pc; do [ -e "$f" ] || missing="$missing $f" done if [ -z "$missing" ]; then bst_ok wavpack/dev-files else bst_notok wavpack/dev-files "absent:$missing" fi rm -rf "$tmp"