#!/usr/bin/env bash # Demonstrate WAI carrying the registered media classes end-to-end on REAL # bytes. WAI's job is carriage + dispatch; the decoder is sink-supplied. So for # each class this: # 1. obtains REAL canonical bytes of the format (where an open encoder is # installed) or a representative payload otherwise, # 2. `wai wrap`s them into an envelope and `wai unwrap`s them back, # 3. asserts the payload is BYTE-IDENTICAL (the §2 carriage guarantee), # 4. hands the extracted bytes to a sink decoder (OpenJPEG / ffmpeg) where one # is installed — the full carry → dispatch → decode path on a real file. # # Honest status per row: # real+decode = real canonical bytes, carried byte-exact, decoded by a real # reference tool acting as the sink (JPEG 2000, IAMF, and the # AVC / HEVC / VP9 / AAC broadcast rows via ffmpeg). # companion = a companion-only capability (SPEC §5: HDR dynamic metadata, # LCEVC) carried the one conforming way, as a WAI2 companion # bound to a base rendition: the companion comes back # byte-exact, the envelope passes the role check, `wai wrap` # refuses the WAI1 form, and a real base decodes. # real+parse = real canonical bytes (a genuine encoder built the bitstream), # carried byte-exact, structurally decoded into the format's # units by a real reference parser as the sink (V-PCC → V3C). # real = real canonical bytes carried byte-exact (sink decoder not # installed here; payload is genuine). # carriage = representative payload carried byte-exact; decoder # sink-supplied (no open encoder installed for the format). # proprietary = carried byte-exact; decoder is proprietary (sink-supplied). # candidate = a registered capability with no pinned payload format (SPEC §5); `wai wrap` must refuse it and write nothing. # # ./tools/wai_carriage_demo.sh # # The volumetric / immersive-audio rows decode for real only when their sink # tools are on PATH (G-PCC tmc3, V-PCC tmc2 + TMC2_CFG, IVAS_cod/IVAS_dec). # `tools/build_reference_decoders.sh` fetches and builds them at pinned # versions and prints the exact PATH/TMC2_CFG to export here. Rows fall back to # byte-exact carriage when a tool is absent. set -uo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" WAI="$ROOT/wai-rs/target/release/wai" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT echo "building wai CLI…" ( cd "$ROOT/wai-rs" && cargo build --release --features codecs --bin wai >/dev/null 2>&1 ) \ || { echo "build failed"; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } src_png="$TMP/src.png" # a small real image (any source) — fall back to a generated gradient if have sips && [ -f "/System/Library/Desktop Pictures/Sonoma.heic" ]; then sips -s format png -Z 128 "/System/Library/Desktop Pictures/Sonoma.heic" --out "$src_png" >/dev/null 2>&1 fi [ -f "$src_png" ] || ffmpeg -hide_banner -loglevel error -y -f lavfi -i "testsrc=size=128x128:duration=1" -frames:v 1 "$src_png" 2>/dev/null printf '\n%-22s %-7s %-13s %s\n' "capability" "fidelity" "status" "round-trip" printf '%-22s %-7s %-13s %s\n' "----------------------" "-------" "-------------" "----------" # $1 capability $2 payload-file $3 status-label $4 decode-cmd (optional; runs on the unwrapped payload) demo() { local cap="$1" pay="$2" status="$3" decode="${4:-}" local env="$TMP/e.wai" out="$TMP/p.out" local tier; tier="$("$WAI" capabilities 2>/dev/null | awk -v c="$cap" '$1==c{print $2}')" "$WAI" wrap "$cap" "$pay" -o "$env" >/dev/null 2>&1 "$WAI" unwrap "$env" -o "$out" >/dev/null 2>&1 local rt="✗ MISMATCH" if cmp -s "$pay" "$out"; then rt="✓ byte-exact"; fi if [ -n "$decode" ]; then if eval "$decode" >/dev/null 2>&1; then rt="$rt → decoded"; else rt="$rt → decode FAILED"; fi fi printf '%-22s %-7s %-13s %s\n' "$cap" "${tier:-?}" "$status" "$rt" } rep() { head -c "${2:-512}" /dev/urandom > "$1"; } # representative payload # ---- cinema: JPEG 2000 (real, decoded by OpenJPEG) ---- if have opj_compress && have opj_decompress; then opj_compress -i "$src_png" -o "$TMP/c.j2k" >/dev/null 2>&1 demo wai.image.jp2 "$TMP/c.j2k" "real+decode" "opj_decompress -i $TMP/p.out -o $TMP/c_dec.png" else rep "$TMP/c.j2k" 4096; demo wai.image.jp2 "$TMP/c.j2k" "carriage" fi # ---- immersive audio: IAMF (real, demuxed by ffmpeg) ---- if have ffmpeg; then ffmpeg -hide_banner -loglevel error -y -f lavfi -i "sine=frequency=440:duration=1,aformat=channel_layouts=stereo" \ -c:a libopus -stream_group "type=iamf_audio_element:id=1:st=0,layer=ch_layout=stereo" \ -stream_group "type=iamf_mix_presentation:id=2:stg=0,submix=parameter_id=100|parameter_rate=48000|element=stg=0:headphones_rendering_mode=stereo" \ -map 0 -f iamf "$TMP/a.iamf" >/dev/null 2>&1 if [ -s "$TMP/a.iamf" ]; then demo wai.audio.iamf "$TMP/a.iamf" "real+decode" "ffmpeg -hide_banner -loglevel error -y -i $TMP/p.out -f f32le $TMP/a.pcm" else rep "$TMP/a.iamf" 2048; demo wai.audio.iamf "$TMP/a.iamf" "carriage"; fi else rep "$TMP/a.iamf" 2048; demo wai.audio.iamf "$TMP/a.iamf" "carriage"; fi # ---- the rest: representative payloads, decoder sink-supplied / proprietary ---- rep "$TMP/v.j2k" 4096; demo wai.video.j2k "$TMP/v.j2k" "carriage" rep "$TMP/ac4" 2048; demo wai.audio.ac4 "$TMP/ac4" "proprietary" # ---- immersive voice/audio: 3GPP IVAS (real, decoded by IVAS_dec) ---- # IVAS_cod / IVAS_dec = the 3GPP TS 26.258 reference (build from # forge.3gpp.org/rep/ivas-codec-pc/ivas-codec). Encode real stereo PCM and # decode it back — a full PCM -> bitstream -> PCM round-trip via the sink. if have IVAS_cod && have IVAS_dec && have ffmpeg; then ffmpeg -hide_banner -loglevel error -y -f lavfi -i "sine=frequency=440:duration=1" \ -f lavfi -i "sine=frequency=660:duration=1" \ -filter_complex "[0:a][1:a]join=inputs=2:channel_layout=stereo[a]" -map "[a]" \ -ar 48000 -f s16le -acodec pcm_s16le "$TMP/ivas_in.pcm" >/dev/null 2>&1 IVAS_cod -stereo 48000 48 "$TMP/ivas_in.pcm" "$TMP/ivas" >/dev/null 2>&1 if [ -s "$TMP/ivas" ]; then demo wai.audio.ivas "$TMP/ivas" "real+decode" "IVAS_dec STEREO 48 $TMP/p.out $TMP/ivas_dec.pcm" else rep "$TMP/ivas" 2048; demo wai.audio.ivas "$TMP/ivas" "carriage"; fi else rep "$TMP/ivas" 2048; demo wai.audio.ivas "$TMP/ivas" "carriage"; fi # ---- 3D geometry: Draco (real, decoded by draco_decoder) ---- if have draco_encoder && have draco_decoder; then python3 - "$TMP/sphere.obj" <<'PY' import math, sys S=16; verts=[]; faces=[] for i in range(S+1): lat=math.pi*(-0.5+i/S) for j in range(S): lon=2*math.pi*j/S verts.append((math.cos(lat)*math.cos(lon), math.sin(lat), math.cos(lat)*math.sin(lon))) for i in range(S): for j in range(S): a=i*S+j+1;b=i*S+(j+1)%S+1;c=(i+1)*S+j+1;d=(i+1)*S+(j+1)%S+1 faces+=[(a,c,b),(b,c,d)] open(sys.argv[1],'w').write(''.join(f'v {x:.5f} {y:.5f} {z:.5f}\n' for x,y,z in verts) +''.join(f'f {a} {b} {c}\n' for a,b,c in faces)) PY draco_encoder -i "$TMP/sphere.obj" -o "$TMP/drc" >/dev/null 2>&1 demo wai.mesh.draco "$TMP/drc" "real+decode" "draco_decoder -i $TMP/p.out -o $TMP/m_dec.obj" else rep "$TMP/drc" 4096; demo wai.mesh.draco "$TMP/drc" "carriage"; fi # ---- point cloud: MPEG G-PCC (real, decoded by tmc3 if built/on PATH) ---- # tmc3 = the MPEG G-PCC reference (build from github.com/MPEGGroup/mpeg-pcc-tmc13). if have tmc3; then python3 - "$TMP/cloud.ply" <<'PY' import math, random, sys random.seed(0); pts=[] for _ in range(2000): u,v=random.random(),random.random(); th=2*math.pi*u; ph=math.acos(2*v-1) pts.append((int(200*math.sin(ph)*math.cos(th))+256, int(200*math.sin(ph)*math.sin(th))+256, int(200*math.cos(ph))+256)) f=open(sys.argv[1],'w') f.write('ply\nformat ascii 1.0\nelement vertex %d\nproperty float x\nproperty float y\nproperty float z\nend_header\n'%len(pts)) [f.write(f'{x} {y} {z}\n') for x,y,z in pts] PY tmc3 --mode=0 --uncompressedDataPath="$TMP/cloud.ply" --compressedStreamPath="$TMP/gpcc" --positionQuantizationScale=1 >/dev/null 2>&1 demo wai.pc.gpcc "$TMP/gpcc" "real+decode" "tmc3 --mode=1 --compressedStreamPath=$TMP/p.out --reconstructedDataPath=$TMP/pc_dec.ply" else rep "$TMP/gpcc" 4096; demo wai.pc.gpcc "$TMP/gpcc" "carriage"; fi # ---- point cloud: MPEG V-PCC (real encode + structural V3C decode if tmc2 built) ---- # tmc2 = the MPEG V-PCC reference (build from github.com/MPEGGroup/mpeg-pcc-tmc2, # bundles its HM video codec). Set TMC2_CFG to the reference cfg/ directory. # Encode is geometry-only (--noAttributes) so the RGB→YUV color pipeline (which # needs HDRTools) is not required; the bitstream is still a conformant V3C stream. if have PccAppEncoder && have PccAppParser && [ -n "${TMC2_CFG:-}" ] && [ -d "${TMC2_CFG:-}" ]; then python3 - "$TMP/vpcc_f0000.ply" <<'PY' import math, random, sys random.seed(1); pts=[] for _ in range(4000): u,v=random.random(),random.random(); th=2*math.pi*u; ph=math.acos(2*v-1); r=128 x=int(r*math.sin(ph)*math.cos(th))+256; y=int(r*math.sin(ph)*math.sin(th))+256; z=int(r*math.cos(ph))+256 pts.append((x,y,z,(x*7)%256,(y*5)%256,(z*3)%256)) f=open(sys.argv[1],'w') f.write('ply\nformat ascii 1.0\nelement vertex %d\nproperty float x\nproperty float y\nproperty float z\nproperty uchar red\nproperty uchar green\nproperty uchar blue\nend_header\n'%len(pts)) [f.write('%d %d %d %d %d %d\n'%p) for p in pts] PY PccAppEncoder --configurationFolder="$TMC2_CFG/" \ --uncompressedDataPath="$TMP/vpcc_f%04d.ply" --compressedStreamPath="$TMP/vpcc" \ --frameCount=1 --startFrameNumber=0 --resolution=1023 --noAttributes=1 --absoluteD1=1 --absoluteT1=1 \ --geometryConfig=hm/ctc-hm-geometry-ai.cfg --attributeConfig=hm/ctc-hm-attribute-ai.cfg \ --occupancyMapConfig=hm/ctc-hm-occupancy-map-ai-main.cfg \ --videoEncoderOccupancyPath="$(command -v PccAppVideoEncoder)" \ --videoEncoderGeometryPath="$(command -v PccAppVideoEncoder)" \ --videoEncoderAttributePath="$(command -v PccAppVideoEncoder)" >/dev/null 2>&1 if [ -s "$TMP/vpcc" ]; then # sink = PccAppParser: structurally decode the carried payload into V3C units. # Parse to a file then grep — a `... | grep -q` pipe SIGPIPEs the parser early # (its dump is large), which trips `set -o pipefail` into a false failure. demo wai.pc.vpcc "$TMP/vpcc" "real+parse" "PccAppParser $TMP/p.out >$TMP/v3c.txt 2>&1; grep -q V3C_VPS $TMP/v3c.txt" else rep "$TMP/vpcc" 4096; demo wai.pc.vpcc "$TMP/vpcc" "carriage"; fi else rep "$TMP/vpcc" 4096; demo wai.pc.vpcc "$TMP/vpcc" "carriage"; fi rep "$TMP/sog" 4096; rm -f "$TMP/sog.wai" if "$WAI" wrap wai.splat.sog "$TMP/sog" -o "$TMP/sog.wai" >/dev/null 2>&1 || [ -e "$TMP/sog.wai" ]; then sog_rt="✗ wrap ACCEPTED a candidate"; else sog_rt="✓ refused, nothing written"; fi printf '%-22s %-7s %-13s %s\n' "wai.splat.sog" "$("$WAI" capabilities 2>/dev/null | awk '$1=="wai.splat.sog"{print $2}')" "candidate" "$sog_rt" echo '{"version":1,"avatars":[]}' > "$TMP/h.hjif" demo wai.haptic.mihs "$TMP/h.hjif" "carriage" # ---- broadcast and streaming codecs ---- # Where ffmpeg has an encoder, build the REAL payload in the framing SPEC §5 pins # (Annex B byte stream / IVF / ADTS) and decode the carried bytes with the # matching raw demuxer FORCED (-f h264 / hevc / ivf / aac), so the row shows a # real decoder consumes exactly the pinned framing. The others carry a # representative payload; their decoders are sink-supplied. # The decode must yield every encoded frame, not merely exit 0: 10 video frames, # and for AAC the frame count of the encoded ADTS stream, counted independently # of the decoder by walking its frame headers. # Encoder list captured once: `ffmpeg -encoders | grep -q` would SIGPIPE ffmpeg # and trip `set -o pipefail` into a false "no encoder". ENCODERS=""; have ffmpeg && ENCODERS="$(ffmpeg -hide_banner -encoders 2>/dev/null)" has_enc() { grep -q " $1 " <<<"$ENCODERS"; } # $1 capability $2 encoder $3 muxer (the pinned framing) $4 extension bcast_video() { local cap="$1" enc="$2" fmt="$3" ext="$4" if has_enc "$enc"; then ffmpeg -hide_banner -loglevel error -y -f lavfi -i "testsrc=size=128x96:rate=10:duration=1" \ -c:v "$enc" -pix_fmt yuv420p -f "$fmt" "$TMP/b.$ext" >/dev/null 2>&1 if [ -s "$TMP/b.$ext" ]; then demo "$cap" "$TMP/b.$ext" "real+decode" \ "ffmpeg -hide_banner -loglevel error -y -f $fmt -i $TMP/p.out -f framemd5 $TMP/d.md5 && [ \$(grep -vc '^#' $TMP/d.md5) -eq 10 ]" return fi fi rep "$TMP/b.$ext" 4096; demo "$cap" "$TMP/b.$ext" "carriage" } bcast_video wai.video.avc libx264 h264 h264 bcast_video wai.video.hevc libx265 hevc hevc bcast_video wai.video.vp9 libvpx-vp9 ivf ivf # Count the ADTS frames of a stream by walking each header's frame_length. # Prints nothing unless the walk lands exactly on the end of the file. adts_frames() { python3 - "$1" <<'ADTS' import sys b = open(sys.argv[1], 'rb').read(); i = n = 0 while i + 7 <= len(b) and b[i] == 0xFF and (b[i+1] & 0xF6) == 0xF0: fl = ((b[i+3] & 3) << 11) | (b[i+4] << 3) | (b[i+5] >> 5) if fl < 7 or i + fl > len(b): break i += fl; n += 1 print(n if n > 0 and i == len(b) else "") ADTS } if has_enc aac; then ffmpeg -hide_banner -loglevel error -y -f lavfi -i "sine=frequency=440:duration=1" \ -c:a aac -f adts "$TMP/b.aac" >/dev/null 2>&1 fi if [ -s "$TMP/b.aac" ]; then AAC_FRAMES="$(adts_frames "$TMP/b.aac")" demo wai.audio.aac "$TMP/b.aac" "real+decode" \ "[ -n '$AAC_FRAMES' ] && ffmpeg -hide_banner -loglevel error -y -f aac -i $TMP/p.out -f framemd5 $TMP/d.md5 && [ \$(grep -vc '^#' $TMP/d.md5) -eq '$AAC_FRAMES' ]" else rep "$TMP/b.aac" 2048; demo wai.audio.aac "$TMP/b.aac" "carriage"; fi for cap in wai.video.av2 wai.video.vvc wai.video.evc wai.video.evc.baseline \ wai.video.avs3 wai.audio.mpegh wai.audio.avs3; do rep "$TMP/b.bin" 4096; demo "$cap" "$TMP/b.bin" "carriage" done # ---- companion-only capabilities: HDR dynamic metadata, LCEVC (SPEC §5) ---- # Their payload means nothing without its base, so the only conforming carriage # is a WAI2 companion bound to a base rendition. Each row wraps base + companion, # requires the role check to pass, unwraps the companion byte-exact, requires # `wai wrap` to refuse the WAI1 form, and (for a real base) decodes the base — # the picture a sink without the companion's decoder presents. The base is a # real AVC Annex B stream when libx264 is installed. The companion payloads are # representative: no open encoder for them is installed here. BASE_CAP=wai.video.avc; BASE="$TMP/base.h264"; BASE_REAL="" if has_enc libx264; then ffmpeg -hide_banner -loglevel error -y -f lavfi -i "testsrc=size=128x96:rate=10:duration=1" \ -c:v libx264 -pix_fmt yuv420p -f h264 "$BASE" >/dev/null 2>&1 [ -s "$BASE" ] && BASE_REAL=1 fi [ -n "$BASE_REAL" ] || rep "$BASE" 4096 # $1 companion capability $2 companion payload [$3 a second payload of the # same capability, which must be refused when stacked on the same base] demo_companion() { local cap="$1" pay="$2" env="$TMP/c.wai" out="$TMP/c.out" ins local tier; tier="$("$WAI" capabilities 2>/dev/null | awk -v c="$cap" '$1==c{print $2}')" rm -f "$env" "$out" "$WAI" wrap "$BASE_CAP" "$BASE" -o "$env" --companion "$cap" "$pay" >/dev/null 2>&1 "$WAI" unwrap "$env" --component 1 -o "$out" >/dev/null 2>&1 local rt="✗ MISMATCH" if cmp -s "$pay" "$out"; then rt="✓ byte-exact"; fi # capture first: `wai inspect | grep -q` would SIGPIPE wai under pipefail ins="$("$WAI" inspect "$env" 2>/dev/null)" if grep -q 'component roles: *✓' <<<"$ins"; then rt="$rt · roles ✓"; else rt="$rt · roles ✗"; fi if "$WAI" wrap "$cap" "$pay" -o "$TMP/refused.wai" >/dev/null 2>&1; then rt="$rt · WAI1 ✗ ACCEPTED"; else rt="$rt · WAI1 refused"; fi if [ -n "${3:-}" ]; then if "$WAI" wrap "$BASE_CAP" "$BASE" -o "$TMP/stacked.wai" --companion "$cap" "$pay" \ --companion "$cap" "$3" >/dev/null 2>&1; then rt="$rt · 2 on 1 base ✗ ACCEPTED"; else rt="$rt · 2 on 1 base refused"; fi fi if [ -n "$BASE_REAL" ]; then rm -f "$TMP/base.out" "$TMP/base.md5" "$WAI" unwrap "$env" -o "$TMP/base.out" >/dev/null 2>&1 if ffmpeg -hide_banner -loglevel error -y -f h264 -i "$TMP/base.out" -f framemd5 "$TMP/base.md5" >/dev/null 2>&1 \ && [ "$(grep -vc '^#' "$TMP/base.md5")" -eq 10 ]; then rt="$rt → base decoded"; else rt="$rt → base decode FAILED"; fi fi printf '%-22s %-7s %-13s %s\n' "$cap" "${tier:-?}" "companion" "$rt" } rep "$TMP/hdr" 256; demo_companion wai.meta.hdr10plus "$TMP/hdr" rep "$TMP/dovi" 256; demo_companion wai.meta.dovi "$TMP/dovi" rep "$TMP/lcevc" 1024; rep "$TMP/lcevc2" 1024 demo_companion wai.video.lcevc "$TMP/lcevc" "$TMP/lcevc2" echo echo "carriage is byte-exact for every class (§2). 'real+decode' rows ran a real" echo "reference decoder (OpenJPEG / ffmpeg) on the unwrapped payload — the full" echo "carry → dispatch → decode path. The rest carry identically; their decoders" echo "are sink-supplied (open) or proprietary, by WAI's design."