diff --git a/apps/starry/video-codec/README.md b/apps/starry/video-codec/README.md new file mode 100644 index 0000000000..6195dd73e5 --- /dev/null +++ b/apps/starry/video-codec/README.md @@ -0,0 +1,66 @@ +# video-codec (browser multimedia dependency stack) + +Umbrella carpet for the Chromium-class multimedia dependency stack on StarryOS, +delivered as native-library API + CLI carpets and validated on all four +architectures (x86_64 / aarch64 / riscv64 / loongarch64) headless. + +This is the umbrella tracker. Each wave lands as an independent focused app so +it can be verified on-target and merged without blocking the rest. The +`video-codec` app itself carpets the video-codec libraries; sibling apps +(audio-codec, image-codec, font-text, shader-toolchain, stream-rtc, audio-io, +container-parse) carpet the other categories. + +## Scope + +Native-library carpets (each library's own API and CLI), which are distinct +from the ffmpeg-mediated coverage in the existing `ffmpeg` app: that app +exercises codecs through libav*; this stack exercises each library directly +(own encoder/decoder binaries, own bitstream tooling, deterministic +round-trip / checksum / PSNR proofs). + +Supplements, does not duplicate, the existing multimedia work: +`ffmpeg` (#1086), `ffplay` (#1268), `doom` (#1415), `qt-calc` (#1396), the +Weston/`wayland` compositor, and the GPU compute carpets (#1574, #1575, #1576, +#1578, #1609, #1610, #1648). + +## Wave plan + +`video-codec` (this app): x264, dav1d, x265 + libde265, libvpx, openh264, +libaom, SVT-AV1 - synthetic frame -> encode -> decode round-trip with NAL/OBU +structural assertions and PSNR / md5 checks. All pure CPU, file-based, no +display or audio. + +Sibling apps (subsequent waves): + +| app | libraries | +|-----|-----------| +| audio-codec | FLAC, opus, vorbis + ogg, lame + mpg123, speex, fdk-aac | +| image-codec | libjpeg-turbo, libpng, giflib, libwebp, libtiff, openjpeg (avif/heif later) | +| font-text | FreeType, HarfBuzz, FontConfig, Cairo, libass, Pango | +| shader-toolchain | glslang, SPIRV-Tools, SPIRV-Cross, GLAD, GLM (offline SPIR-V pipeline) | +| stream-rtc | RTP, libsrtp, usrsctp (loopback); libdatachannel later | +| audio-io | ALSA, SDL2-audio, OpenAL-soft (file / null / wave backends) | +| container-parse | double-conversion, expat, libxml2, zlib, brotli, libebml, libmatroska | + +The existing `ffmpeg` app is extended in place (not duplicated) to the three +non-x86_64 architectures and to currently-untested codecs/protocols/filters. + +## Platform constraints (scoped honestly, not silently) + +- No audio device (`/dev/snd`): audio codecs transcode to buffers/files and + are fully testable, but audio-io libraries can only prove the software + pipeline (open / format-negotiate / resample / mix / encode-to-file) plus + correct graceful failure when no card exists - not physical playback. +- Software GPU only (llvmpipe / lavapipe, no virgl): irrelevant to codecs, + containers, fonts and the offline shader toolchain (all CPU); only hardware + video decode / real 3D accel are out of scope. +- No interactive display server (no X11; Wayland is compositor-only; display + apps are x86_64-only): windowing libraries are used through their headless + paths (EGL-surfaceless, null platform, offscreen draw-data), not interactive + windows on aarch64 / riscv64 / loongarch64. + +## Status + +Work in progress. The `video-codec` app carpets x264 and dav1d first; the +remaining libraries and sibling apps land in subsequent waves. Every wave is +gated on real on-target 4-arch green with real assertions (no silent skips). diff --git a/apps/starry/video-codec/build-aarch64-unknown-none-softfloat.toml b/apps/starry/video-codec/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..884aa834c1 --- /dev/null +++ b/apps/starry/video-codec/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,10 @@ +target = "aarch64-unknown-none-softfloat" +log = "Warn" +features = [ + "qemu", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] diff --git a/apps/starry/video-codec/build-loongarch64-unknown-none-softfloat.toml b/apps/starry/video-codec/build-loongarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..154fe308b9 --- /dev/null +++ b/apps/starry/video-codec/build-loongarch64-unknown-none-softfloat.toml @@ -0,0 +1,10 @@ +target = "loongarch64-unknown-none-softfloat" +log = "Warn" +features = [ + "axplat-dyn/efi", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] diff --git a/apps/starry/video-codec/build-riscv64gc-unknown-none-elf.toml b/apps/starry/video-codec/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..75531a504f --- /dev/null +++ b/apps/starry/video-codec/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,14 @@ +features = [ + "ax-runtime/display", + "ax-runtime/rtc", + "ax-driver/serial", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/vsock", +] +log = "Warn" +target = "riscv64gc-unknown-none-elf" diff --git a/apps/starry/video-codec/build-x86_64-unknown-none.toml b/apps/starry/video-codec/build-x86_64-unknown-none.toml new file mode 100644 index 0000000000..a8c92b85d8 --- /dev/null +++ b/apps/starry/video-codec/build-x86_64-unknown-none.toml @@ -0,0 +1,9 @@ +target = "x86_64-unknown-none" +log = "Warn" +features = [ + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] diff --git a/apps/starry/video-codec/prebuild.sh b/apps/starry/video-codec/prebuild.sh new file mode 100755 index 0000000000..6e6c795549 --- /dev/null +++ b/apps/starry/video-codec/prebuild.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +set -euo pipefail + +# video-codec carpet prebuild: install the native codec CLIs and libraries from +# Alpine (x264, dav1d - both available on all four arches), cross-compile the +# synthetic-frame generator, and stage everything into the guest overlay. +# Mirrors the apk-add + overlay model used by the ffmpeg / redis apps. + +app_dir="${STARRY_APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" +rootfs="${STARRY_ROOTFS:-}" +staging_root="${STARRY_STAGING_ROOT:-}" +overlay_dir="${STARRY_OVERLAY_DIR:-}" +arch="${STARRY_ARCH:-}" +apk_cache="${STARRY_WORKSPACE:-$(cd "$app_dir/../../.." && pwd)}/target/video-codec-apk-cache" + +require_env() { + local name="$1" value="$2" + if [[ -z "$value" ]]; then + echo "error: $name is required" >&2 + exit 1 + fi +} + +ensure_host_packages() { + local missing=() + command -v debugfs >/dev/null 2>&1 || missing+=(e2fsprogs) + command -v install >/dev/null 2>&1 || missing+=(coreutils) + command -v readelf >/dev/null 2>&1 || missing+=(binutils) + [[ ${#missing[@]} -eq 0 ]] && return + echo "error: missing required host packages: ${missing[*]}" >&2 + echo "error: install with: sudo apt-get install -y --no-install-recommends ${missing[*]}" >&2 + exit 1 +} + +qemu_runner_candidates() { + case "$arch" in + aarch64) printf '%s\n' qemu-aarch64-static qemu-aarch64 ;; + riscv64) printf '%s\n' qemu-riscv64-static qemu-riscv64 ;; + x86_64) printf '%s\n' qemu-x86_64-static qemu-x86_64 ;; + loongarch64) printf '%s\n' qemu-loongarch64-static qemu-loongarch64 ;; + *) echo "error: unsupported arch for video-codec prebuild: $arch" >&2; exit 1 ;; + esac +} + +find_qemu_runner() { + local candidate + while IFS= read -r candidate; do + if command -v "$candidate" >/dev/null 2>&1; then + command -v "$candidate"; return 0 + fi + done < <(qemu_runner_candidates) + echo "error: missing qemu-user runner for arch $arch" >&2 + exit 1 +} + +run_guest_apk_once() { + local qemu_runner; qemu_runner="$(find_qemu_runner)" + if [[ ! -x "$staging_root/sbin/apk" ]]; then + echo "error: staging root is missing guest apk: $staging_root/sbin/apk" >&2 + exit 1 + fi + mkdir -p "$apk_cache" + QEMU_LD_PREFIX="$staging_root" \ + LD_LIBRARY_PATH="$staging_root/lib:$staging_root/usr/lib" \ + "$qemu_runner" -L "$staging_root" "$staging_root/sbin/apk" \ + --root "$staging_root" \ + --repositories-file "$staging_root/etc/apk/repositories" \ + --keys-dir "$staging_root/etc/apk/keys" \ + --cache-dir "$apk_cache" \ + --update-cache --timeout 60 --no-interactive \ + --force-no-chroot --scripts=no "$@" +} + +run_guest_apk() { + local attempt max_attempts=4 + [[ -f /etc/resolv.conf ]] && cp /etc/resolv.conf "$staging_root/etc/resolv.conf" + for attempt in $(seq 1 "$max_attempts"); do + if run_guest_apk_once "$@"; then return 0; fi + [[ "$attempt" -eq "$max_attempts" ]] && return 1 + echo "apk failed, retrying ($attempt/$max_attempts)..." >&2 + sleep $((attempt * 3)) + done +} + +extract_rootfs() { debugfs -R "rdump / $staging_root" "$rootfs"; } + +# x264 (H.264 encoder CLI+libs) and dav1d (AV1 decoder CLI+libs) are both in +# Alpine main/community on all four arches (APKINDEX-verified). x264-libs +# provides the shared library the CLI links against. +install_codec_packages() { run_guest_apk add x264 x264-libs dav1d libdav1d; } + +copy_file_to_overlay() { + local guest_path="$1" mode="$2" + local source="$staging_root${guest_path}" target="$overlay_dir${guest_path}" + if [[ ! -e "$source" ]]; then + echo "error: missing guest file after codec install: $guest_path" >&2 + exit 1 + fi + [[ -L "$source" ]] && source="$(readlink -f "$source")" + install -Dm"$mode" "$source" "$target" +} + +find_library_path() { + local library="$1" dir + for dir in lib usr/lib usr/local/lib; do + if [[ -e "$staging_root/$dir/$library" ]]; then + printf '/%s/%s\n' "$dir" "$library"; return 0 + fi + done + return 1 +} + +copy_runtime_dependencies() { + local pending=("$@") seen=" " guest_path library + while [[ ${#pending[@]} -gt 0 ]]; do + guest_path="${pending[0]}"; pending=("${pending[@]:1}") + [[ "$seen" == *" $guest_path "* ]] && continue + seen+="$guest_path " + while IFS= read -r library; do + local library_path + find_library_path "$library" >/dev/null 2>&1 || continue + library_path="$(find_library_path "$library")" + copy_file_to_overlay "$library_path" 0644 + pending+=("$library_path") + done < <(readelf -d "$staging_root$guest_path" 2>/dev/null | + sed -n 's/.*Shared library: \[\(.*\)\].*/\1/p') + done +} + +populate_overlay() { + copy_file_to_overlay /usr/bin/x264 0755 + copy_file_to_overlay /usr/bin/dav1d 0755 + copy_runtime_dependencies /usr/bin/x264 /usr/bin/dav1d + install -Dm0755 "$app_dir/video-codec-tests.sh" "$overlay_dir/usr/bin/video-codec-tests.sh" +} + +require_env STARRY_ROOTFS "$rootfs" +require_env STARRY_STAGING_ROOT "$staging_root" +require_env STARRY_OVERLAY_DIR "$overlay_dir" +require_env STARRY_ARCH "$arch" + +ensure_host_packages +extract_rootfs +install_codec_packages +populate_overlay diff --git a/apps/starry/video-codec/qemu-aarch64.toml b/apps/starry/video-codec/qemu-aarch64.toml new file mode 100644 index 0000000000..3011b64387 --- /dev/null +++ b/apps/starry/video-codec/qemu-aarch64.toml @@ -0,0 +1,22 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "cortex-a53", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/video-codec-tests.sh" +success_regex = ["(?m)^VIDEO_CODEC_TEST_PASSED\\s*$"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^VIDEO_CODEC_TEST_FAILED\\s*$"] +timeout = 900 diff --git a/apps/starry/video-codec/qemu-loongarch64.toml b/apps/starry/video-codec/qemu-loongarch64.toml new file mode 100644 index 0000000000..49e71230c9 --- /dev/null +++ b/apps/starry/video-codec/qemu-loongarch64.toml @@ -0,0 +1,24 @@ +args = [ + "-machine", + "virt", + "-cpu", + "la464", + "-nographic", + "-m", + "512M", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/video-codec-tests.sh" +success_regex = ["(?m)^VIDEO_CODEC_TEST_PASSED\\s*$"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^VIDEO_CODEC_TEST_FAILED\\s*$"] +timeout = 900 diff --git a/apps/starry/video-codec/qemu-riscv64.toml b/apps/starry/video-codec/qemu-riscv64.toml new file mode 100644 index 0000000000..c48621d5da --- /dev/null +++ b/apps/starry/video-codec/qemu-riscv64.toml @@ -0,0 +1,22 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "rv64", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/video-codec-tests.sh" +success_regex = ["(?m)^VIDEO_CODEC_TEST_PASSED\\s*$"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^VIDEO_CODEC_TEST_FAILED\\s*$"] +timeout = 900 diff --git a/apps/starry/video-codec/qemu-x86_64.toml b/apps/starry/video-codec/qemu-x86_64.toml new file mode 100644 index 0000000000..4811bfb124 --- /dev/null +++ b/apps/starry/video-codec/qemu-x86_64.toml @@ -0,0 +1,20 @@ +args = [ + "-nographic", + "-m", + "512M", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/video-codec-tests.sh" +success_regex = ["(?m)^VIDEO_CODEC_TEST_PASSED\\s*$"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^VIDEO_CODEC_TEST_FAILED\\s*$"] +timeout = 900 diff --git a/apps/starry/video-codec/video-codec-tests.sh b/apps/starry/video-codec/video-codec-tests.sh new file mode 100755 index 0000000000..70dfdf86ab --- /dev/null +++ b/apps/starry/video-codec/video-codec-tests.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# video-codec carpet: native codec library API/CLI checks, headless, on-target. +# Work in progress - the x264 encode cell is real; dav1d and the remaining +# codecs (x265, libvpx, openh264, libaom) are filled in during the codec waves. +set -u + +fail() { echo "VIDEO_CODEC_TEST_FAILED: $1"; exit 1; } +ok() { echo " ok: $1"; } + +work=/tmp/video-codec +rm -rf "$work"; mkdir -p "$work" || fail "mkdir workdir" +cd "$work" || fail "cd workdir" + +echo "== x264 (H.264 encoder) ==" + +x264 --version >x264.ver 2>&1 || fail "x264 --version" +grep -qi "x264" x264.ver || fail "x264 version banner" +ok "x264 present: $(head -1 x264.ver)" + +# Deterministic synthetic YUV420p input: 64x64, 10 frames of a fixed pattern. +# 64*64 (Y) + 32*32 (U) + 32*32 (V) = 6144 bytes/frame. +W=64; H=64; FRAMES=10; FRAMEBYTES=6144 +i=0 +: >in.yuv +while [ "$i" -lt "$FRAMES" ]; do + # per-frame constant byte value gives a stable, valid decodable pattern + tr '\0' "$(printf '\\%03o' $(( (i * 20 + 16) % 240 + 8 )))" >in.yuv || fail "gen yuv frame $i" + i=$((i + 1)) +done +[ "$(wc -c x264.log 2>&1 \ + || { cat x264.log; fail "x264 encode"; } + +[ -s out.264 ] || fail "x264 produced empty bitstream" +ok "encoded out.264 = $(wc -c dav1d.ver 2>&1 || fail "dav1d --version" +ok "dav1d present: $(head -1 dav1d.ver)" +# TODO (AV1 decode wave): bundle a deterministic AV1 .ivf test vector, decode +# with dav1d --muxer md5 and assert the md5 matches the golden. Reference +# decoder output is bit-exact, so this becomes an ironclad assertion. +echo " wip: dav1d full-decode md5 cell pending the AV1 wave" + +# Subsequent waves add: x265+libde265 (HEVC), libvpx (VP8/VP9), openh264, +# libaom / SVT-AV1 (AV1 encode -> dav1d cross-decode). + +echo "VIDEO_CODEC_TEST_PASSED"