. "$BST_LIB" # sbc 2.0 (poky recipes-multimedia/sbc) -- the Bluetooth low-complexity subband # codec library and the sbcenc / sbcdec / sbcinfo utilities packaged with it. # # THE FIRST THING TO KNOW IS THAT sbcenc DOES NOT READ WAV, AND NEITHER TOOL # TOUCHES libsndfile. The ticket asked for a WAV round trip; the program takes # Sun/NeXT .au, S16_BE, and nothing else -- it reads a 24-byte au_header, # rejects anything whose encoding is not AU_FMT_LIN16 with "Not in Sun/NeXT # audio S16_BE format", and sets sbc.endian = SBC_BE on the strength of it. # sbcdec -f writes the same format back. libsndfile appears in DEPENDS for # src/sbctester, which is noinst_PROGRAMS and is never installed. So the round # trip below is .au -> .sbc -> .au, which is the same assertion in the format # the tools actually speak. # # WHAT IS ASSERTABLE HERE, ON A GUEST WITH NO SOUND DEVICE. SBC is lossy, so the # round trip cannot be a cmp -- but everything that decides whether the codec # WORKS is offline and exact: the frame length is fixed by the bitstream format # for a given (subbands, blocks, bitpool, channel mode), the frame count follows # from the input length, sbcinfo reads every parameter back out of the header # bits, and the decoded audio has to line up with the input to within the # filterbank's delay and stay near it. Those are the cases below. # # THE ROUND TRIP HAS TO BE ALIGNED BEFORE IT CAN BE MEASURED, and getting that # wrong is the trap this file is built around. SBC's analysis and synthesis # filterbanks each delay the signal; comparing the two files at offset 0 # measures the delay and not the coding error. sbc-aucmp searches the lag that # minimises the RMS and reports it, and the suite asserts the value: 73 samples # for 8 subbands and 37 for 4, which is (10-1)*subbands+1 -- the delay of the # 10*subbands-tap prototype filter, so a red there is a real change in the # filterbank and not a threshold that drifted. # # WHY THE FIXTURE IS A CHIRP AND WHY IT IS COMPUTED IN INTEGERS. The alignment # search needs a signal that autocorrelates to ONE peak: against the two fixed # tones this file started with (common period 64) the search returned lag 55, # which is the true delay aliased by the period and reads exactly like a # filterbank delay -- a green case asserting nothing. A sweep has no such # ambiguity, and it also puts energy in every subband in turn. Integers rather # than libm because the bitstream digests below are pinned: sin() is not # correctly rounded, so a fixture built on it differs in its last place between # architectures and no digest computed from it could be compared. # # THE DIGESTS ARE THE POINT OF THE WHOLE FILE. SBC is fixed-point integer # arithmetic end to end, so its output should not depend on the architecture at # all -- and it does not: the four bitstreams pinned here are byte-for-byte the # streams an x86-64 build of the same source produces from the same fixture, # measured on the build host, where the library selects its SSE primitives and # here it selects the portable C ones. That is a far stronger statement than an # error bound, which would pass on an encoder that was consistently slightly # wrong. # # NO THREADS, NO DEVICE, NO NETWORK: sbc is single-threaded and file-to-file, # and the one device path in the tools (sbcdec without -f, which ioctls # SNDCTL_DSP_* on /dev/dsp) is not exercised because this image has no OSS # device. That is a property of the guest, not a gap in the port. require sbcenc sbc/present P=/usr/libexec/sbc-tests tmp=${TMPDIR:-/tmp}/bst-sbc.$$ mkdir -p "$tmp" 2>/dev/null ref=$tmp/ref.au mono=$tmp/mono.au # sbcenc writes the bitstream to stdout, so every encode goes through here -- # `check` runs its argv in a command substitution, which a shell function is # reachable from. enc() { out=$1; shift; sbcenc "$@" > "$out"; } md5of() { md5sum < "$1" 2>/dev/null | cut -d' ' -f1; } # The value of one key of sbc-aucmp's report. cmpkey() { sed -n "s/^$2 //p" "$1"; } # ------------------------------------------------------------------- probes --- # Cross-compiled by the recipe's own bbappend into sbc-tests, because # brintos-selftests is allarch and cannot compile C. Their absence is a # packaging fault on this image, not a legitimately missing tool, so it is a red # rather than the `require` skip that guards sbcenc. missing="" for f in sbc-augen sbc-aucmp sbc-libprobe; do [ -x "$P/$f" ] || missing="$missing $P/$f" done if [ -n "$missing" ]; then bst_notok sbc/probes "absent:$missing -- the round-trip cases below cannot run" rm -rf "$tmp" exit 0 fi bst_ok sbc/probes check_out sbcenc/version "SBC encoder utility ver 2.0" sbcenc --help check_out sbcdec/version "SBC decoder utility ver 2.0" sbcdec --help # sbcinfo with no argument exits 1, so this cannot go through check_out (which # requires rc 0) -- and the status is half the assertion. uo=$(sbcinfo 2>&1); urc=$? case "$uo" in *"Usage: sbcinfo "*) uhit=1 ;; *) uhit=0 ;; esac if [ "$urc" -eq 1 ] && [ "$uhit" -eq 1 ]; then bst_ok sbcinfo/usage else bst_notok sbcinfo/usage "rc=$urc out:'$uo'" fi # ------------------------------------------------------------------ fixture --- # 8192 sample frames of stereo at 44.1 kHz: 24-byte header + 8192*2*2 bytes. check sbc/fixture "$P/sbc-augen" "$ref" 44100 2 8192 check_eq sbc/fixture-size 32792 "$(wc -c < "$ref" 2>/dev/null | tr -d ' ')" if [ "$(wc -c < "$ref" 2>/dev/null | tr -d ' ')" != "32792" ]; then bst_notok sbc/fixture-usable "the fixture was not written -- remaining cases not run" rm -rf "$tmp" exit 0 fi # The fixture itself is architecture-independent by construction, and every # digest below is computed from it -- so if this one moves, they all move for a # reason that has nothing to do with the codec. It is asserted first for that # reason: a red here is the explanation for a red on any of them. if command -v md5sum >/dev/null 2>&1; then check_eq sbc/fixture-digest 30641e3cc67e9a6f7c65744917f141e5 "$(md5of "$ref")" else bst_skip sbc/fixture-digest "md5sum not installed on this image" fi # --------------------------------------------------- the encode, and sbcinfo --- # 8 subbands, 16 blocks, joint stereo, bitpool 32: 4 header bytes + 8 scale-factor # bytes + ceil((8 + 16*32)/8) = 65, so 77 bytes a frame, and 8192/128 = 64 frames. e32=$tmp/e32.sbc check sbc/encode-b32 enc "$e32" -s 8 -B 16 -b 32 -j "$ref" check_eq sbc/encode-b32-size 4928 "$(wc -c < "$e32" 2>/dev/null | tr -d ' ')" info=$tmp/info32.txt sbcinfo "$e32" > "$info" 2>&1 check_out sbcinfo/subbands "Subbands 8" cat "$info" check_out sbcinfo/blocks "Block length 16" cat "$info" check_out sbcinfo/frequency "Sampling frequency 44.1 kHz" cat "$info" check_out sbcinfo/mode "Channel mode Joint Stereo" cat "$info" check_out sbcinfo/allocation "Allocation method Loudness" cat "$info" check_out sbcinfo/bitpool "Bitpool 32" cat "$info" check_out sbcinfo/frames "Number of frames 64" cat "$info" check_out sbcinfo/framelen "Frame length 77 Bytes" cat "$info" check_out sbcinfo/msbc "mSBC 0" cat "$info" # ------------------------------------------------------------- the round trip --- d32=$tmp/d32.au r32=$tmp/r32.txt check sbcdec/decode-b32 sbcdec -f "$d32" "$e32" if [ -s "$d32" ] && "$P/sbc-aucmp" "$ref" "$d32" 128 > "$r32" 2>&1; then bst_ok sbc/compare-b32 check_eq sbc/roundtrip-rate 44100 "$(cmpkey "$r32" 'tst rate')" check_eq sbc/roundtrip-channels 2 "$(cmpkey "$r32" 'tst channels')" check_eq sbc/roundtrip-frames 8192 "$(cmpkey "$r32" 'tst frames')" # (10-1)*8+1, the 80-tap prototype filter's delay. check_eq sbc/roundtrip-lag 73 "$(cmpkey "$r32" lag)" # The error bound. Measured 1 permille RMS / 18 permille peak at this # bitpool; the bounds are set well above so that a threshold nobody can # justify is not what a future red means, while still being far below the # signal (a decoder emitting silence reads about 250 permille RMS here). rms=$(cmpkey "$r32" rmse_permille) peak=$(cmpkey "$r32" peak_permille) if [ -n "$rms" ] && [ "$rms" -le 10 ]; then bst_ok sbc/roundtrip-rmse else bst_notok sbc/roundtrip-rmse "rmse ${rms:-?} permille of full scale, bound 10" fi if [ -n "$peak" ] && [ "$peak" -le 60 ]; then bst_ok sbc/roundtrip-peak else bst_notok sbc/roundtrip-peak "peak error ${peak:-?} permille of full scale, bound 60" fi else bst_notok sbc/compare-b32 "sbc-aucmp could not compare the round trip: $(head -3 "$r32" 2>/dev/null | tr '\n' ' ')" fi # ------------------------------------------------------- bitpool is load-bearing --- # The ticket's second case. Frame length is fixed by the format, so the encoded # size is an exact function of the bitpool and not a compression outcome: # 4 + 8 + ceil((8 + 16*B)/8) bytes a frame, 64 frames. e16=$tmp/e16.sbc e53=$tmp/e53.sbc check sbc/encode-b16 enc "$e16" -s 8 -B 16 -b 16 -j "$ref" check sbc/encode-b53 enc "$e53" -s 8 -B 16 -b 53 -j "$ref" check_eq sbc/encode-b16-size 2880 "$(wc -c < "$e16" 2>/dev/null | tr -d ' ')" check_eq sbc/encode-b53-size 7616 "$(wc -c < "$e53" 2>/dev/null | tr -d ' ')" check_out sbcinfo/framelen-b16 "Frame length 45 Bytes" sbcinfo "$e16" check_out sbcinfo/framelen-b53 "Frame length 119 Bytes" sbcinfo "$e53" # ...and a bigger bitpool has to buy something. A codec that ignored the # parameter would still pass every size case above, because those are format # arithmetic; only this one says the extra bits reach the audio. d16=$tmp/d16.au d53=$tmp/d53.au r16=$tmp/r16.txt r53=$tmp/r53.txt if sbcdec -f "$d16" "$e16" >/dev/null 2>&1 && sbcdec -f "$d53" "$e53" >/dev/null 2>&1 && "$P/sbc-aucmp" "$ref" "$d16" 128 > "$r16" 2>&1 && "$P/sbc-aucmp" "$ref" "$d53" 128 > "$r53" 2>&1; then p16=$(cmpkey "$r16" peak_permille) p32=$(cmpkey "$r32" peak_permille) p53=$(cmpkey "$r53" peak_permille) if [ -n "$p16" ] && [ -n "$p32" ] && [ -n "$p53" ] && [ "$p16" -gt "$p32" ] && [ "$p32" -gt "$p53" ]; then bst_ok sbc/bitpool-improves-audio else bst_notok sbc/bitpool-improves-audio "peak error should fall with bitpool: 16->${p16:-?} 32->${p32:-?} 53->${p53:-?}" fi else bst_notok sbc/bitpool-improves-audio "the bitpool-16 or bitpool-53 round trip did not complete" fi # ------------------------------------------------------- 4 subbands, 4 blocks, mono --- # The other end of every axis the format has, and the one that shows the delay # is a property of the filterbank rather than a constant: (10-1)*4+1 = 37. emono=$tmp/mono.sbc dmono=$tmp/monoback.au rmono=$tmp/rmono.txt check sbc/fixture-mono "$P/sbc-augen" "$mono" 16000 1 4096 check sbc/encode-mono enc "$emono" -s 4 -B 4 -b 20 "$mono" check_eq sbc/encode-mono-size 4096 "$(wc -c < "$emono" 2>/dev/null | tr -d ' ')" check_out sbcinfo/mono-mode "Channel mode Mono" sbcinfo "$emono" check_out sbcinfo/mono-subbands "Subbands 4" sbcinfo "$emono" check_out sbcinfo/mono-blocks "Block length 4" sbcinfo "$emono" check_out sbcinfo/mono-framelen "Frame length 16 Bytes" sbcinfo "$emono" check sbcdec/decode-mono sbcdec -f "$dmono" "$emono" if [ -s "$dmono" ] && "$P/sbc-aucmp" "$mono" "$dmono" 128 > "$rmono" 2>&1; then check_eq sbc/mono-rate 16000 "$(cmpkey "$rmono" 'tst rate')" check_eq sbc/mono-channels 1 "$(cmpkey "$rmono" 'tst channels')" check_eq sbc/mono-frames 4096 "$(cmpkey "$rmono" 'tst frames')" check_eq sbc/mono-lag 37 "$(cmpkey "$rmono" lag)" else bst_notok sbc/mono-roundtrip "sbc-aucmp could not compare the mono round trip" fi # ------------------------------------------------------------- the bitstreams --- # Byte for byte what an x86-64 build of sbc 2.0 produces from the same fixture, # measured on the build host on 2026-08-18. Nothing in SBC is floating point, so # this is the claim the arm should be able to make; the error bounds above would # not distinguish an encoder that was consistently a little bit wrong from one # that is right. if command -v md5sum >/dev/null 2>&1; then check_eq sbc/bitstream-b16 78a4143ea8d23377e47c85bc1da981bc "$(md5of "$e16")" check_eq sbc/bitstream-b32 0cb8a388a5c1edbd7e08fa536d9d0aef "$(md5of "$e32")" check_eq sbc/bitstream-b53 f5f0e6be775c25c81f42d7b42f969cb2 "$(md5of "$e53")" check_eq sbc/bitstream-mono 0f43ee7141b13de516956a8ea3846bba "$(md5of "$emono")" else bst_skip sbc/bitstream-b16 "md5sum not installed on this image" bst_skip sbc/bitstream-b32 "md5sum not installed on this image" bst_skip sbc/bitstream-b53 "md5sum not installed on this image" bst_skip sbc/bitstream-mono "md5sum not installed on this image" fi # ------------------------------------------------------------ refusals are real --- # Every assertion above is about a stream the tools accepted. These are the ones # they must not: a decoder that accepted anything would make sbcinfo's readback # meaningless, and an encoder that ignored its header check would encode noise # from a WAV and report success. # A real 44-byte canonical RIFF/WAVE header -- 8 kHz mono S16_LE, no samples. # It has to be at least 24 bytes long or sbcenc never reaches its format check # and reports a short read instead, which is a different refusal and would leave # the one under test unexercised (it did, on the first run of this file). notau=$tmp/notau.wav { printf 'RIFF'; printf '\44\0\0\0'; printf 'WAVEfmt '; printf '\20\0\0\0' printf '\1\0\1\0'; printf '\100\37\0\0'; printf '\200\76\0\0' printf '\2\0\20\0'; printf 'data'; printf '\0\0\0\0'; } > "$notau" check_eq sbcenc/non-au-fixture-size 44 "$(wc -c < "$notau" 2>/dev/null | tr -d ' ')" check_out sbcenc/rejects-non-au "Not in Sun/NeXT audio S16_BE format" sbcenc "$notau" junk=$tmp/junk.sbc dd if="$ref" of="$junk" bs=1 skip=24 count=300 2>/dev/null check_rc sbcinfo/rejects-garbage 1 sbcinfo "$junk" # Mono input with -j is a contradiction the encoder is required to catch, not to # silently encode as stereo. check_out sbcenc/rejects-joint-mono "Audio is mono but joint or dualchannel mode has been specified" \ sbcenc -j "$mono" # --------------------------------------------------------------- the library --- # sbc-libprobe is the only thing on this image that takes sbc/sbc.h and # libsbc.a out of the sysroot the way a downstream port would -- sbcenc and # sbcdec link the build tree's private convenience library and never touch the # installed archive. lp=$tmp/libprobe.txt if "$P/sbc-libprobe" > "$lp" 2>&1; then bst_ok sbc/libprobe check_out sbc/libprobe-codesize "codesize 512" cat "$lp" check_out sbc/libprobe-framelen "frame_length 77" cat "$lp" check_out sbc/libprobe-parsed "parsed_bitpool 32" cat "$lp" # MMX, SSE, IWMMXT, ARMv6 and NEON are all behind SBC_BUILD_WITH_*_SUPPORT, # set by configure from the host CPU. wasm32 is none of them, so the portable # path is what must be selected here -- and if one of the others ever were, # this arm would be running hand-written assembly for a different machine. check_out sbc/libprobe-generic-c "implementation Generic C" cat "$lp" else bst_notok sbc/libprobe "rc=$? $(head -3 "$lp" 2>/dev/null | tr '\n' ' ')" fi # ------------------------------------------------------------ the packaged half --- missing="" for f in /usr/lib/libsbc.a /usr/include/sbc/sbc.h /usr/lib/pkgconfig/sbc.pc; do [ -e "$f" ] || missing="$missing $f" done if [ -z "$missing" ]; then bst_ok sbc/dev-files else bst_notok sbc/dev-files "absent:$missing" fi # 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 sbc/static-only "/usr/lib/libsbc.so" "/usr/lib/libsbc.so.1" rm -rf "$tmp"