Claude/test coverage analysis 2viujv - #12
Merged
Merged
Conversation
Document the hardware H.264 encoder capabilities (resolution limits, input pixel format, ROI/motion-vector features, dual-stream), the software decoder path, the C API with usage examples, the ESPHome integration and recommended sdkconfig for ESP32-P4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…or ESP32-P4 Introduce h264_hp, an internal ESPHome component wrapping edge264 (BSD) to decode H.264 Baseline/Main/High on ESP32-P4, where the existing software path (esp_h264_dec_sw -> tinyH264/h264bsd) only handles Constrained Baseline and the bundled libopenh264.a is encoder-only. - C++ wrapper H264HpDecoder: begin/decode_nal/decode_annexb/get_frame with PSRAM allocation callbacks and dual-core threads. - Outputs I420 (Y/Cb/Cr) matching ip_camera_viewer's expected format. - Compiles as no-op until edge264 is vendored (git submodule), guarded by USE_H264_HP_EDGE264; ESPHome codegen + CMake auto-detect the sources. - README documents the RISC-V port plan, PSRAM usage, and realistic performance expectations (scalar, no SIMD on P4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
… + honest decoder logs Diagnose 'Instruction address misaligned on core 1' at boot: init_h264_decoder_() runs unconditionally in setup() (so RESTORE_DEFAULT_OFF can't prevent it) and esp_h264_dec_sw_new -> h264bsdAlloc spawns a tinyH264 worker pinned to core 1 from the prebuilt libtinyh264.a, which faults with a misaligned-instruction exception on ESP32-P4. - esp_h264/__init__.py: set CONFIG_ESP_H264_DUAL_TASK=0 to drop the core-1 worker (confirmation + mitigation; real fix is the edge264-based h264_hp). - ip_camera_viewer.cpp: stop logging 'openh264 supports Baseline/Main/High' — the bundled libopenh264.a is encoder-only and decoding is tinyH264 Baseline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…ESP32-P4 Vendor the edge264 decoder source (552 KB) under components/h264_hp/edge264/ (edge264.h + src/, unity build via src/edge264.c). edge264 supports Baseline/ Main/High (CABAC, 8x8 IDCT, B-slices) — the lightweight 'VLC-like' path the tinyH264/h264bsd decoder (Baseline only) lacks. Build is GATED and OFF by default so existing GCC builds stay green: - edge264 internal.h requires SSE/NEON/WASM or Clang vector builtins; on RISC-V with GCC it hits '#error No supported vector intrinsics'. So edge264 only compiles with the ESP-IDF Clang toolchain. - CMake compiles edge264/src/edge264.c (unity) + defines USE_H264_HP_EDGE264 ONLY when the marker edge264/ENABLE_EDGE264 exists. Otherwise the wrapper stays no-op. - README documents the Clang requirement, activation steps, scalar perf expectations, and the GCC-friendly alternative (openh264 decoder from source). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…ressif/esp-h264-component) Faithful replacement of the esp_h264 upstream tree with v1.3.6 (libs + headers + sources all internally consistent), preserving the ESPHome glue (__init__.py, docs, NOTICE). Why: the boot 'Instruction address misaligned on core 1' crash came from the prebuilt tinyH264 dual-task worker. 1.3.6 ships freshly-built P4 libs (libtinyh264.a 2.45MB->2.8MB, libopenh264.a) and defaults ESP_H264_DUAL_TASK to 'n' — aligning with our crash mitigation. - interface sources moved interface/include/src -> interface/src; updated ip_camera_viewer_build.py paths accordingly. - Decoder still tinyH264/h264bsd = Constrained Baseline only (officially confirmed: no Main/High SW decoder, no HW decoder on ESP32-P4). - Note: needs an on-hardware compile pass (no toolchain available here). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…build Pivot the edge264 ESP32-P4 strategy to the robust path: instead of porting edge264 to GCC (it relies on Clang-only builtins __builtin_shufflevector / __builtin_elementwise_* / __builtin_reduce_*, which would mean reimplementing ~30 vector primitives blind with silent-correctness risk), compile edge264 ONCE with esp-clang into a RISC-V static lib and LINK it into the ESPHome/GCC build. RISC-V Clang/GCC ABI is compatible — same mechanism as libtinyh264.a. - build_libedge264_esp32p4.sh: one-time esp-clang build -> edge264/lib/esp32p4/ libedge264.a (unity build of src/edge264.c; SIMD=CLANG native tested backend). - CMakeLists: link libedge264.a + define USE_H264_HP_EDGE264 when present; no-op otherwise so default builds stay green. pthread provided by ESP-IDF. - README/__init__.py: document the build-with-Clang / link-with-GCC flow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…ed no Clang ESPHome users compile via PlatformIO/GCC and have neither ESP-IDF nor esp-clang, so they cannot run build_libedge264_esp32p4.sh. Move that one-time Clang build to CI: a workflow installs esp-clang, builds the RISC-V static lib, and commits edge264/lib/esp32p4/libedge264.a back to the repo (like libtinyh264.a). End users then just 'esphome compile' — no toolchain required. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…csr/zifencei) CI build of libedge264.a failed: clang-16 rejects 'rv32imafc_zicsr_zifencei' (unsupported extension 'zicsr'). esp-clang accepts plain rv32imafc for ESP32-P4. Toolchain detection, sysroot and the build flow itself all worked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…-clang build edge264 compiled almost fully with esp-clang; the only error was an undeclared CLOCK_PROCESS_CPUTIME_ID in get_relative_time_us() (a profiling/log helper), which ESP-IDF newlib gates behind _POSIX_CPUTIME. Remap it to CLOCK_MONOTONIC (available) via -D so the unity build compiles. Also drop C++-only flags (-fno-exceptions/-fno-rtti) that were unused on the C source. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The esp-clang build SUCCEEDED and produced libedge264.a; only the commit-back step failed with 'fatal: not in a git directory' — git refuses the workspace inside the espressif/idf container due to ownership. Add git config --global --add safe.directory and push explicitly to GITHUB_REF_NAME. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…elds
edge264.h Edge264Frame exposes width_Y/height_Y, stride_Y/stride_C and
frame_crop_offsets{top,right,bottom,left}. Map them in H264HpDecoder::get_frame
(display size = coded size minus crop; planes at coded origin with strides),
replacing the placeholder zeros.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…ward) The esp-clang build + commit worked (libedge264.a created), but the push was rejected because the branch advanced during the build (concurrent commits). Make the commit step fetch + rebase our binary-only commit onto the remote tip and retry, so the push fast-forwards cleanly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Wire the High Profile decoder into the camera pipeline (guarded by USE_H264_HP_EDGE264, set when libedge264.a is present): - AUTO_LOAD h264_hp; h264_hp defines USE_H264_HP_EDGE264 globally when the lib is built. - On SPS profile detection, non-Baseline streams set use_hp_decoder_ instead of logging 'won't decode'. - decode_h264_to_yuv_: lazily begin the edge264 decoder, feed Annex-B, and pack the strided Y/Cb/Cr planes into contiguous I420 in yuv_buffer_ (the layout convert_yuv420_to_rgb565_ expects). Baseline still uses tinyH264. Needs an on-hardware esphome compile + run to validate link and decode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Validate the real risk: does libedge264.a (built with esp-clang) LINK into a GCC ESP-IDF build for esp32p4, with its system deps (pthread/sysconf/malloc/ clock_gettime) resolved? A minimal idf project references all edge264 public symbols and a CI workflow runs idf.py build (no execution). This is exactly the link an ESPHome firmware needs; it surfaces any undefined references to fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…conf) The CI link test built the whole esp32p4 firmware and proved edge264.o (esp-clang) links into a GCC build (Clang<->GCC RISC-V ABI OK). The only unresolved symbol was 'sysconf': libedge264.a was appended last, outside the link group, so newlib's sysconf wasn't re-scanned. Use ESP-IDF add_prebuilt_library so the lib joins the --start-group/--end-group and cross-deps with newlib resolve regardless of order. Applied to both the link test and the h264_hp component. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…valid)
ip_camera_viewer AUTO_LOADs h264_hp, but its cv.invalid schema rejected the
empty config that AUTO_LOAD injects ('Component not found' / invalid). Use an
empty schema so the internal component loads cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…is ignored)
ESPHome does not use an external component's CMakeLists.txt, so the edge264
include dir and libedge264.a link were never applied -> 'edge264.h: No such file'.
Handle both via ESPHome's mechanisms (same as esp_h264):
- h264_hp/__init__.py: cg.add_build_flag('-I.../edge264') so the wrapper finds
edge264.h (in addition to -DUSE_H264_HP_EDGE264).
- ip_camera_viewer_build.py: link libedge264.a (PlatformIO LINKFLAGS) with
-Wl,-u,sysconf to resolve edge264's newlib sysconf reference regardless of order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
ESPHome runs __init__.py from the copied src tree, where esp_h264 was absent unless explicitly listed -> the -I flags were skipped -> 'esp_h264_dec.h: No such file'. AUTO_LOAD esp_h264 (and keep h264_hp) so ESPHome copies it; its headers become available and the existing -I logic fires. Required esp_h264 schema change from cv.invalid to empty so AUTO_LOAD does not fail. Also make the edge264 include robust: use the relative subdir path 'edge264/edge264.h' instead of relying on a -I flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
- h264_hp: -I must point at the component dir (parent of edge264/),
since the source includes "edge264/edge264.h"; the previous -I on
edge264/ itself produced .../edge264/edge264/edge264.h (not found).
- ip_camera_viewer: use abspath for component_dir so the os.path.exists
gate no longer silently drops the esp_h264 -I flags (which caused
'esp_h264_dec.h: No such file or directory').
- build script: receive the absolute component dir via a custom
PlatformIO option instead of Dir('.') (which is the project dir in an
extra_script), so esp_h264 sources and libedge264.a link correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Reproduces a real `esphome compile` of the ip_camera_viewer component on ESP32-P4 (esp-idf) in CI, so include-path and prebuilt-lib link errors are caught here instead of on a tester's hardware. Base config mirrors ESPHome's mipi_dsi test; WiFi via esp32_hosted mirrors the M5Stack Tab5 config. Runtime video output is not validated (requires hardware). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
esp_h264_dec_cfg_t (1.3.6) exposes only pic_type; there is no profile_idc member nor ESP_H264_PROFILE_AUTO enum. The sw decoder routes to tinyH264/ h264bsd (Constrained Baseline only); Main/High is handled by the edge264 (h264_hp) path. Removes the invalid assignment that broke the build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The final firmware link failed with undefined references to edge264_alloc, edge264_decode_NAL, edge264_get_frame, etc. Cause: the archive was added to LINKFLAGS, which SCons emits BEFORE the object files. GNU ld only extracts archive members that resolve already-undefined symbols, so edge264 (placed before h264_hp_decoder.cpp.o) was discarded. Moving it to LIBS (as a File node) puts it after the objects, matching the passing CI link-test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Adds ipcv_p4_lvglfork_test.yaml which sources lvgl (+ forked siblings button/font/image/thorvg/lottie_state_machine) from youkorr/lvgl_9.5@main instead of the LVGL bundled with ESPHome. The workflow now builds both the stock-LVGL and fork-LVGL configs in parallel (matrix), so we can confirm ip_camera_viewer compiles against the user's actual LVGL 9.5.0 fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…a component) esphome config rejected the list because thorvg has no __init__.py (it's a build-included library, not an ESPHome component), and button is auto-loaded by lvgl rather than listed explicitly. Reduce the external_components filter to [lvgl], matching how youkorr actually uses the fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The fork's lvgl_esphome.h includes esphome/components/image/image.h and font/font.h (guarded by USE_IMAGE/USE_FONT). Those components must be loaded for the headers to be present, matching youkorr's real configs which list [lvgl, image, font]. Previously only [lvgl] was listed, causing 'image/image.h: No such file' when compiling ip_camera_viewer against the fork. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Listing image/font in external_components only makes them available; a component's source (and header) is copied only when actually used. The fork's lvgl_esphome.h includes image.h/font.h under USE_IMAGE/USE_FONT, so we add minimal font:/image: blocks (gfonts + a tiny PNG) to instantiate them, exactly like a real youkorr config. Resolves 'image/image.h: No such file'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
youkorr builds from claude/jolly-lamport-ERZOC, not main (main has a broken standalone-compiled lv_freertos.c). Test ip_camera_viewer against the branch actually used. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The two existing configs pull the components with `type: local`, which keeps the component source at a stable checkout path for the whole build. Real users (and the tester) use `type: git`, where ESPHome clones the component into .esphome/external_components/. Because ESPHome's component copy is non-recursive and extension-filtered, the nested headers (h264_hp/edge264/edge264.h, esp_h264/interface/include/esp_h264_dec.h) and the prebuilt libedge264.a are never copied into the build tree — they only resolve via the custom -I flags pointing back at the clone. This config exercises that exact path so the failure reproduces in CI instead of only on hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…gned_alloc) On hardware the RTP fix works (data flows, NALs parse, frames assemble), but edge264_alloc() now fails repeatedly: "edge264_alloc a échoué". edge264 allocates its decoder struct (measured sizeof = 41984 bytes) with aligned_alloc(64, ...), which on ESP-IDF is served from INTERNAL DRAM only. With LVGL + the MIPI-DSI display running, internal DRAM is too tight at that point, so the allocation returns NULL and High Profile decode never starts. (The big per-frame YCbCr buffers already go to PSRAM via the wrapper's alloc callback; only this one-time struct used internal DRAM.) Add psram_alloc.c providing __wrap_aligned_alloc and a global -Wl,--wrap=aligned_alloc link option (via idf_build_set_property in the edge264 IDF component): aligned allocations >= 8 KB are served from PSRAM (heap_caps_aligned_alloc, MALLOC_CAP_SPIRAM), smaller ones stay internal. This catches edge264's struct without a libedge264.a rebuild. edge264_free()'s plain free() handles PSRAM memory transparently on ESP-IDF. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…solves The dev (native CMake/Ninja) link failed: "undefined reference to __wrap_aligned_alloc". -Wl,--wrap=aligned_alloc rewrites edge264's calls to __wrap_aligned_alloc, but that symbol lives in psram_alloc.c inside this component's static archive; ld resolves the wrapped calls before considering that archive member, so it is never pulled in. Add -Wl,-u,__wrap_aligned_alloc to force the linker to load it from the start. Harmless for the PlatformIO/released builds (which already linked it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The tester's RTP now works fully (FU-A frames assembled, "First H264 frame: 47144 bytes") but edge264_alloc still fails. Disambiguate "wrap not in the build (old commit)" vs "PSRAM genuinely unavailable": on failure, log free/largest block for PSRAM and INTERNAL, and emit a one-shot trace from __wrap_aligned_alloc showing whether it routed to PSRAM and whether PSRAM returned a block. One log then tells us exactly which case it is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Tester's heaps are healthy (PSRAM 24 MB free, internal 352 KB free) yet edge264_alloc fails and the PSRAM wrap trace never appears -> __wrap_aligned_alloc is not intercepting in his build. Likely either his ESPHome-dev snapshot doesn't honor idf_build_set_property(LINK_OPTIONS) from a path-added managed component, or ESP-IDF's aligned_alloc rejects edge264's struct size (not a multiple of 64). - Attach --wrap/-u via BOTH idf_build_set_property AND component target_link_options (INTERFACE), so at least one path applies across dev snapshots. - begin(): one-shot in-place probe calling aligned_alloc(64,42048) (multiple of 64) vs (64,42000) (not) — conclusively distinguishes "newlib enforces size%align & wrap inactive" from "heap/wrap OK", independent of the wrap trace. - wrap trace now logs the first large (>=20 KB) calls so it lands in the captured window (after camera start), not only at boot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The decoder struct (~41 KB) is allocated by edge264 via plain aligned_alloc(). ESPHome's psram component configures CONFIG_SPIRAM_USE_CAPS_ALLOC, under which PSRAM is reachable only through heap_caps_* and NOT through malloc/aligned_alloc. So edge264 only ever sees internal DRAM, which is exhausted at runtime (the reported 352 KB "internal free" is mostly 32-bit IRAM that aligned_alloc can't use), and edge264_alloc fails even though 24 MB of PSRAM is free. Switch the SPIRAM Kconfig choice to USE_MALLOC (with ALWAYSINTERNAL=16 KB so small/DMA allocations stay internal): malloc/aligned_alloc can then draw the struct from PSRAM. Crucially this does NOT depend on the -Wl,--wrap link flag propagating (which the tester's ESPHome-dev snapshot apparently drops); it fixes edge264_alloc directly. The wrap stays as belt-and-suspenders. Technique confirmed by youkorr/test2_esp_video_esphome PSRAM_PATCHES (CONFIG_SPIRAM_USE_MALLOC). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…ails Conclusive on-device diagnostic: aligned_alloc(64, ~42KB) returns PSRAM addresses (0x487…) — so the 41 KB struct allocation now succeeds in PSRAM (CONFIG_SPIRAM_USE_MALLOC + wrap). edge264_alloc still returned NULL, which means it fails AFTER the struct alloc: with n_threads>0 it calls pthread_create() for worker threads, whose FreeRTOS stacks must live in internal RAM; on this setup (LVGL + MIPI-DSI + SPIRAM in malloc mode) that fails, so edge264_alloc cleans up and returns NULL. Retry edge264_alloc with n_threads=0 (the path all host tests used, decoding 20/20 frames): it returns right after the struct alloc, no threads created. Single-threaded scalar decode is slower but functional — first light. Threading can be re-enabled later once worker-stack placement is sorted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
THE actual cause of edge264_alloc returning NULL on device (struct alloc itself succeeds in PSRAM, confirmed by the 0x487… probe addresses). libedge264.a is built without HAS_LOGS, so edge264_alloc does `if (log_cb) return free(dec), NULL;` right after allocating the struct — before any thread setup. The wrapper passed a real log_cb, so the alloc failed every time (both 2-thread and the single-thread fallback). All host tests passed NULL for log_cb, which is why they decoded fine. Pass nullptr as log_cb. Now edge264_alloc clears the log gate; n_threads=0 returns the decoder immediately, and the multi-thread path is still tried first (with the single-thread fallback if pthread_create fails). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
With log_cb=nullptr, edge264_alloc now succeeds and spawns its worker threads — but one faults: "Fault, core 1, PC=worker_loop, BT=pthread_task_func". edge264 creates workers with pthread_create(..., NULL, ...), so they get CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT (~3 KB). H.264 decode (CABAC/IDCT/ deblock) needs far more, overflowing the stack and faulting in worker_loop. Raise CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT to 32 KB so the worker threads have room. (Stacks land in PSRAM under USE_MALLOC, so internal RAM is unaffected.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The tinyH264/h264bsd Baseline decoder was created eagerly in setup() for
every RTSP/H264 stream. esp_h264_dec_open() spawns a prebuilt RISC-V worker
task that faults ("Instruction address misaligned", core 1) on the ESP32-P4.
High-profile streams are decoded by edge264 (h264_hp) and never need that
worker, yet it was spawned anyway and crashed the device at boot.
Defer the tinyH264 creation: setup()/loop() no longer init it; it is created
on first use in decode_h264_to_yuv_(), only on the Baseline path. The early
guard there no longer requires h264_decoder_ != nullptr, so the edge264 path
runs even though the tinyH264 decoder is never created.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…screen, no overflow) The edge264 path packed YUV at the stream resolution and let convert_yuv420_to_rgb565_ read it at the configured width_/height_. On a resolution mismatch that meant: stream bigger than config -> frame rejected by the size guard (black screen); stream smaller -> convert read uninitialized PSRAM past the packed rows (garbage edges). Lay the decoded frame out at the CONFIGURED size instead: clamp copy extents to min(stream, config), cropping when the stream is larger and letterboxing (black, via a one-time memset) when it is smaller. The frame now always fits exactly in yuv_buffer_ (= width_*height_*3/2), so it can never overflow, and a mismatch always renders an image instead of a black screen. Nominal case (stream == config) is unchanged: full-frame copy. Validated on host end-to-end (edge264 decode -> pack -> RGB565 convert) with real High Profile streams at 640x480/640x360 and mismatched configs: all cases produce frames with a guard-page check showing zero overflow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Device logs show the full pipeline working (boot, WiFi, RTSP DESCRIBE/SETUP/ PLAY, High-profile detection, edge264 init with 2 threads, frames #1..#10 fed) then a reboot DURING decode. The stored crash is "Instruction address misaligned" on core 1 — the classic signature of a corrupted return address from a stack overflow, and core 1 is where an edge264 pthread worker runs. Host measurement: a full 640x480 High-profile decode uses ~20 KB of stack. On RISC-V plus FreeRTOS/ISR overhead the previous 32 KB pthread stack is too tight, so the worker overflows mid-decode. (The host never crashes because its pthread stacks are 8 MB.) Fix: - edge264 worker pthread stack 32 KB -> 96 KB (CONFIG_PTHREAD_TASK_STACK_SIZE_ DEFAULT), ~4-5x the measured usage. - Use a SINGLE edge264 worker (begin(1) instead of begin(2)): sequential decode (clean get_frame draining per timer tick), half the internal RAM, and no inter-worker races — covering both the stack and any 2-thread race as causes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
The stack-overflow crash is gone (no more "instruction address misaligned"). The new device crash is a Task Watchdog timeout (task_wdt_isr -> esp_restart): heavy decode starves the idle task long enough to trip the 5s WDT. Single-worker decode (begin(1)) was too slow to keep up with the realtime 640x480 High stream, so a core stayed busy continuously. Restore 2 workers (begin(2)) — now safe against the original overflow thanks to the 96 KB pthread stacks — so each core falls back to idle between frames and can feed the WDT. Also raise CONFIG_ESP_TASK_WDT_TIMEOUT_S 5s -> 30s to absorb the startup burst (first IDR + edge264 init + first LVGL flush) without rebooting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…adlock The live device crash was a Task Watchdog timeout with loopTask (CPU1) blocked and BOTH cores idle — the signature of a deadlock, not CPU saturation. In multi-threaded mode edge264 spawns pthread workers; on ESP-IDF's FreeRTOS pthreads, loopTask blocks inside decode_NAL/get_frame waiting on a worker that never makes progress (both cores fall to IDLE), so the watchdog reboots. The earlier "instruction address misaligned" reports were all stale PREVIOUS-BOOT records with an idle backtrace, not live decode faults. Linux pthreads never reproduced the deadlock on the host. Switch ip_camera_viewer to begin(0): fully synchronous decode, no worker threads, no deadlock (host-proven 30/30 frames). Decoding now runs on loopTask (~20 KB stack measured), so the device config must set esp32: framework: advanced: loop_task_stack_size: 32768 Drop the now-pointless 96 KB pthread stack override (no edge264 workers exist anymore); keep the 30 s Task WDT timeout as insurance for startup bursts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…e decode crash
Decoding the real Reolink High-Profile stream crashed the ESP32-P4 with a
"Load access fault" in slice decode (and SIGFPE when reproduced on x86-32).
Root cause traced to refs_to_mask() returning a phantom dependency 0x80000000
for IDR slices (whose RefPicList entries are all -1). That bit-31 came from
pow2x4(), whose CLANG/generic backend was:
static u32x4 pow2x4(i32x4 a) {return (u32x4)set32(1) << a;}
pow2x4 must return 0 when the element is negative/out of range (frame indices
are 0..31; unused entries are -1, widened to 255). The SSE/NEON/WASM backends
get this for free because their variable-shift instructions (sllv/...) saturate
to 0 for shift counts >= 32. The generic vector shift does NOT: on targets with
a hardware vector variable-shift (x86-64 vpsllvd) LLVM lowers it to a saturating
shift (returns 0, works), but on targets WITHOUT one (x86-32 SSE2, ESP32-P4
rv32imafc) it scalarizes to 1 << (a & 31) = 1 << 31 = 0x80000000.
Result: every IDR got a fake dependency on frame 31, the task was never marked
ready, worker_loop hit __builtin_ctz(ready_tasks==0) == 32 (UB), read
dec->tasks[32] out of bounds (zeroed) -> pic_width_in_mbs == 0 -> divide by zero
(masked to a wild pointer on RISC-V -> load fault).
Fix: mask the shift to a defined range and zero results for indices >= 32
(unsigned), matching sllv semantics:
((u32x4)set32(1) << (a & set32(31))) & (u32x4)((u32x4)a < (u32x4)set32(32))
Verified on the friend's actual Reolink capture: the x86-32 + generic-CLANG
build (same code path as the rv32 device) went from SIGFPE to decoding all 80
frames with 0 errors and a correct image. SSE 64-bit build unchanged (no
regression). libedge264.a must be rebuilt from this source for the device.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
… miscompile) After the pow2x4 source fix, the device got past slice decode but then hung for 30s inside deblock_mb (Task WDT reboot, loopTask spinning on CPU 1). Bisected: - x86-64 (SSE), x86-32 (SSE), x86-32 (no-SSE, full scalarization) all decode the real Reolink stream correctly (80 frames, 0 errors). - The CI-built lib (esp-clang from IDF 5.3, LLVM 16) hung in deblock on-device. So the hang is an esp-clang/LLVM-16 RISC-V codegen bug, not a source bug. Built edge264 for rv32imafc/ilp32f with clang 18 instead, and validated under qemu-riscv32 running the ACTUAL rv32 instructions on the friend's real capture: 30 frames decoded through deblock_mb with a checksum BIT-IDENTICAL to the x86-64 reference (FR=30 CK=814761264), no hang. This .a is a drop-in replacement: same exported symbols and same undefined symbols (aligned_alloc/free/mem*/snprintf/sysconf/clock_gettime/pthread_*) as the esp-clang build, so it links into the ESPHome/GCC app identically. With begin(0) no pthread function is ever called. NOTE: the build-edge264 workflow still uses esp-clang from IDF 5.3 and would re-introduce the bug if it runs; it should be moved to a newer esp-clang (IDF 5.5+) or this clang-18 path. It only triggers on edge264 src changes, so this lib-only commit does not re-trigger it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
… + newer esp-clang CI [skip ci] The pow2x4 + clang18 fixes got decoding working on the friend's capture, but the LIVE stream still rebooted via Task Watchdog: loopTask spinning in deblock_mb for 30 s. deblock_mb has no internal loop, so the runaway is worker_loop's "while (next_deblock_addr < CurrMbAddr)" iterating on a CurrMbAddr corrupted by some macroblock pattern present in the live feed but not the 8 s capture. Add a safety bound: a valid CurrMbAddr is always < pic_width*pic_height, so cap the loop at the frame size. No-op for valid streams (qemu-riscv32 still decodes the real capture bit-exact, CK=814761264); prevents the runaway/reboot when CurrMbAddr is bad. libedge264.a rebuilt with clang 18 (avoids the esp-clang LLVM-16 deblock miscompile). build-edge264 CI bumped to esp-clang from IDF 5.5 for future rebuilds. [skip ci] so the workflow doesn't rebuild over the clang18 lib. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
Diagnosis from an ESPHome contributor's analysis (issue #28): the High-Profile
8x8 path faults on the ESP32-P4 because it uses aligned wide accesses on the
sample plane — loada64/loada128 and raw *(int64_t*) stores — which x86/ARM
tolerate when misaligned but RISC-V traps on ("Fault - Unknown" in add_idct8x8).
The Baseline 4x4 path survives because it only uses 4-byte accesses. This also
explains why qemu-user never reproduced it: qemu emulates misaligned access
transparently; real P4 hardware traps.
Fixes:
- edge264_internal.h: for __riscv, route loada32/64/128/32x4/64x2 through memcpy
(trap-free narrow accesses, identical values), and add storea32/storea64.
- edge264_residual.c: the 8x8 IDCT's *(int64_t*)SADDR(...) stores -> storea64().
- h264_hp_decoder.cpp: psram_alloc_cb now makes ONE contiguous block with mbs
carved right after samples, matching edge264 internal_alloc's contract (some
code reaches mbs as samples+samples_size; two separate allocs could break it).
Verified under qemu-riscv32 on the friend's real capture: decode output is
bit-identical (FR=30 CK=814761264, all 80 frames), so no regression; the memcpy
form removes the alignment assumption that traps on hardware.
libedge264.a rebuilt with clang 18 from the patched source. [skip ci] so the
esp-clang workflow doesn't rebuild over it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…SC-V The add_idct8x8 alignment fix worked, but the fault moved to deblock_mb: the deblocking filter and intra/residual kernels ALSO read/write the sample plane with raw aligned wide accesses (*(i8x16*), *(int64_t*), *(int32_t*) on SADDR(...)). The chroma plane is only 8-aligned, so 16-byte *(i8x16*) accesses trap on the P4 (x86/ARM tolerate; qemu emulates -> never reproduced there). Convert every raw aligned wide access in the hot kernels to the trap-safe helpers (memcpy-based on __riscv): loada128/loada64 for loads, storea128/ storea64/storea32 for stores. Added storea128. The 4-byte *(int32_t*) loads are left as-is (naturally 4-aligned, never trap). Verified bit-exact under qemu-riscv32 on the friend's capture: FR=30 CK=814761264 (unchanged), full 80 frames — no logic change, just trap-free access. libedge264.a rebuilt with clang 18. [skip ci]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…SC-V) Whack-a-mole confirmed the diagnosis: after deblock, the fault moved to decode_intraChroma. The aligned wide accesses are spread across ALL kernels and use several addressing forms (SADDR, DADDR, bare pointers like *(i8x16*)dst, *(int64_t*)(p += stride)). The chroma plane is only 8-aligned so any 16-byte *(i8x16*) or 8-byte *(int64_t*) access traps on the P4. Comprehensive pass: every raw *(i8x16*) and *(int64_t*) load/store in headers/inter/intra/residual/deblock/slice/mvpred is now routed through loada128/loada64/storea128/storea64 (memcpy-based on __riscv, identical values). 4-byte *(int32_t*) accesses are left (naturally 4-aligned, never trap). No raw wide plane access remains. Verified bit-exact under qemu-riscv32 on the friend's capture (FR=30 CK=814761264, 80 frames). libedge264.a rebuilt with clang 18. [skip ci]. The crash can no longer move to "the next kernel" — they are all converted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…away) After the comprehensive alignment fix the misalignment FAULT is gone, but on the live stream the slice decoder now ran away: loopTask spun ~27s in add_idct8x8 -> Task Watchdog reboot. The macroblock loop's natural end-of-frame guard (mby >= pic_height_in_mbs) only fires when mbx reaches the row end; if pic_width_in_mbs is corrupted to a large value mid-decode, mbx never gets there, mby never advances, and the loop calls add_idct8x8 forever. Capture the real frame size (pic_width_in_mbs * pic_height_in_mbs) BEFORE the loop and return once CurrMbAddr reaches it — robust even if pic_width is later clobbered. No-op for valid streams (CurrMbAddr never exceeds the frame), so the qemu decode is bit-identical (FR=30 CK=814761264). libedge264.a rebuilt with clang 18. [skip ci]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…p ci] The previous runaway guard derives its limit from ctx->t.pic_width_in_mbs * pic_height_in_mbs, captured once before the macroblock loop. That only protects against corruption that strikes AFTER the limit is computed. If ctx->t is clobbered earlier, the limit itself is wrong and the loop can still run away, calling add_idct8x8 forever until the Task Watchdog reboots the board. Add a constant, incorruptible ceiling (65536 MBs, ~5000x3000 — far above anything this embedded target ever decodes) so an infinite slice loop is now physically impossible regardless of when the corruption happens. It never truncates a legitimate frame on this hardware. Verified bit-exact on the rv32 scalar (no-SIMD/CLANG) path: FR=30 CK=814761264, identical to before. libedge264.a rebuilt with clang 18. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
… [skip ci] Real-hardware log (ESP32-P4, live Reolink stream): loopTask hung 25 s with MEPC inside deblock_mb -> Task Watchdog reboot. The decode reached the first IDR frame, then spun. Root: the deblock driver loop "while (next_deblock_addr < _deblock_end)" only had its UPPER end capped (496cdbc). A corrupted next_deblock_addr (large negative, from a clobbered ctx->t) keeps the condition true for ~2 billion increments, deblock_mb walking c._mb into wild PSRAM the whole time — exactly the 25 s hang seen. MEPC in flash-XIP (0x40xxxxxx, not PSRAM) confirms a real runaway loop, not slowness. Fix: sanitize the frame size and add an incorruptible iteration guard so the loop runs at most _total_mbs+1 times regardless of how badly next_deblock_addr is corrupted. A deblock Task-Watchdog reboot is now physically impossible. Also confirmed via a faithful RISC-V alignment check: the actual rv32 build (CLANG backend + __riscv memcpy routing) compiled with -fsanitize=alignment -fsanitize-trap=alignment decodes the full capture with ZERO misaligned-access traps (CK=814761264). The remaining UBSan hits on the x86 build are SSE-backend artifacts and do NOT apply to RISC-V — the alignment hardening is complete; this WDT was a loop runaway, not an alignment fault. libedge264.a rebuilt with clang 18. Bit-exact: FR=30 CK=814761264. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
edge264's get_bytes() loads an unaligned 16-byte SIMD chunk and may touch up to ~16 bytes past the NAL end passed to edge264_decode_NAL (those bytes are masked out of the bitstream but are still read). h264_buffer_ was allocated exactly MAX_H264_SIZE and filled up to that size, so a last NAL ending near the limit could make edge264 read a few bytes past the PSRAM allocation. Over-allocate the H264 input buffer by a 64-byte guard and zero its tail. h264_buffer_size_ is unchanged (fill logic untouched) — the guard is pure slack behind valid data. Verified on host (ASan): with the input buffer padded, the decoder is completely clean (no out-of-bounds reads or writes) across all test streams. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
… slice [skip ci] This is the silent corruption behind the live-stream crashes (deblock_mb / add_idct8x8 watchdog reboots). Found by fuzzing: truncating a single slice NAL (which simulates an RTP packet loss on a live stream) reliably reproduces it, while the clean 8 s file capture never does — exactly the observed pattern. Mechanism: - parse_slice_layer_without_partitioning sets a task's "ready" bit (headers.c ~1291) ONLY when every reference frame it depends on is already decoded. - A slice that references a frame MISSING from the DPB (reference lost to packet loss, or a truncated/corrupt NAL) leaves ready_tasks == 0. - Single-thread mode (begin(0)) then calls worker_loop unconditionally (~1299). - worker_loop did `__builtin_ctz(c.d->ready_tasks)` with ready_tasks == 0, which is UNDEFINED and yields a wild task_id, so `c.t = c.d->tasks[task_id]` reads a whole Edge264Task (~1.6 KB) OUT OF BOUNDS. c.t (pic_width_in_mbs, next_deblock_addr, the bitstream pointers...) is filled with garbage and the decode collapses a few macroblocks later — the "silent bug earlier, crash later" the edge264 author described. With asserts compiled out (-DNDEBUG, the on-device build) the existing assert() does not catch it. Fix: guard worker_loop entry — if ready_tasks == 0, bail cleanly (single-thread returns, multi-thread waits) instead of ctz(0) and corrupting state. Reproduction on host (AddressSanitizer, NDEBUG to mirror the device build): before: heap-buffer-overflow in __asan_memcpy (worker_loop, headers.c:470) after : 400 truncated-NAL trials decode with no ASan error Clean decode unchanged, bit-exact: FR=30 CK=814761264. libedge264.a rebuilt with clang 18. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
… on re-enable) Toggling the camera switch off then on failed with "Failed to allocate aligned RGB565 buffers (614400 bytes each)". free_buffers_() released the RGB565 / H264 / YUV buffers but NOT the edge264 High Profile decoder, which holds the largest PSRAM consumer of all — several MB of DPB frame buffers. Left allocated across the toggle, PSRAM stayed too full/fragmented to reallocate the 1.2 MB of RGB565 double buffers on re-enable. Free the decoder (hp_decoder_.end(), reset hp_started_) at the top of free_buffers_(). It is lazily re-created on the first decode after re-enable, so this is safe and releases the memory that was blocking the realloc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…(fix ENOBUFS freeze) On-device VERBOSE log showed the decoder freezing after ~10 frames with edge264_decode_NAL returning 105 (ENOBUFS) on every subsequent slice, while decode_errors stayed 0 — the classic "one image then frozen" symptom. Root cause: get_frame() borrows a frame (borrow=1), which does NOT clear the frame's output_frames bit — edge264 keeps the DPB slot reserved until the caller returns it via edge264_return_frame(dec, return_arg), where edge264_return_frame: dec->output_frames &= ~(size_t)return_arg; release_frame() was calling edge264_return_frame(dec, nullptr), i.e. output_frames &= ~0 — a NO-OP. So borrowed frames were never actually returned; output_frames filled up and after max_dec_frame_buffering (~10) slots the decoder had no free buffer and returned ENOBUFS forever → output frozen on the first image. Fix: remember the frame's return_arg (1<<pic) from get_frame and pass it to edge264_return_frame in release_frame, so the DPB slot is actually released. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…4 decode time
The IDR decode takes ~15 s and the L2-cache sdkconfig tweak had no effect, so
before concluding it's a hardware memory-bandwidth limit we need two on-device
numbers that were never measured:
1. Real CPU frequency (esp_rom_get_cpu_ticks_per_us). If the P4 is not running
at its 360-400 MHz max, decode is slowed proportionally — a low default freq
would explain the whole thing and is a one-line sdkconfig fix.
2. Isolated edge264 decode time (esp_timer around decode_annexb), separated from
the YUV->RGB conversion and the LVGL render that the existing "lvgl took a
long time" warning lumps together.
Host measurement: one 640x480 IDR = ~140M RISC-V instructions. At 400 MHz that is
~0.35 s of pure compute; the device takes ~15 s, i.e. CPI ~40-60 (memory-stall
bound) OR the CPU is clocked far below 400 MHz. These logs tell us which.
Diagnostics only — no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
…n I-frames) Confirmed with the ESPHome dev: the ~15 s "lvgl took a long time" is the I-frame edge264 decode (~11 s) running on loopTask, which freezes screen/touch/audio. P-frames (~150 ms) are fine. edge264 can't be made real-time on this scalar P4 (no HW H.264 decoder, memory-bound), so the fix is to stop it BLOCKING the UI. Move the H.264 pipeline (fetch RTP + edge264 decode + YUV->RGB565) onto a dedicated FreeRTOS task. loopTask's lv_timer callback now only PRESENTS a finished frame (canvas update + swap) — instant. The device stays fluid even during the 11 s I-frame decode (which now runs in the background). Details: - Producer/consumer handshake via std::atomic<bool> decode_frame_ready_ with acquire/release ordering (RISC-V is weakly ordered; the converted buffer must be visible before the flag is observed). Task fills current_decode_buffer_ then store(true, release); loopTask load(acquire), displays, swaps, store(false). - LVGL calls stay on loopTask only (LVGL is not thread-safe). - Task stack 28 KB in INTERNAL RAM (PSRAM stack is unusable: a flash/NVS write during the long decode disables the cache -> crash). - Defensive: if the task can't be created (internal RAM), decode_task_handle_ stays null and we fall back to in-line decode (old behavior, may freeze). - On disable, when the task is active we DON'T free buffers / disconnect (the task may be mid-decode) — just pause; avoids use-after-free. Requirements for this to help (config): - Task WDT timeout >= 30 s (h264_hp sets it) so the 11 s decode doesn't starve the idle task into a reboot. - Enough internal RAM for the 28 KB task stack: reduce loop_task_stack_size (loopTask no longer decodes) and/or drop micro_wake_word. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xq4a4ojwv286DNunwtewfZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary by cubic
Adds H.264 High Profile decoding on ESP32‑P4 via the new
h264_hp(edge264) component and updatesesp_h264to v1.3.6. Improves RTSP reliability, prevents P4 crashes, and makes builds work cleanly with ESPHome’s CMake and PlatformIO; CI now prebuilds and link‑testslibedge264.a.New Features
h264_hp(edge264), with PSRAM allocation, RISC‑V alignment‑safe memory access, and loop guards to avoid WDT timeouts.ip_camera_viewer: auto‑route Main/High to edge264 and Baseline to tinyH264; robust RTP‑over‑TCP reassembly; deterministic Annex‑B split; crop/letterbox to the configured size; background decode task to keep the UI responsive; decode/CPU‑freq perf logs.esp_h264v1.3.6 as an IDF component (targetsesp32s3,esp32s31,esp32p4) with updated headers/libs and Kconfig; default single‑task decoder to avoid the P4 dual‑task boot crash.CI/Build
esp_h264andedge264as ESP‑IDF components and removeip_camera_viewer_build.py.edge264with esp‑clang intolibedge264.aand commits it; a GCC link test on ESP32‑P4 usesadd_prebuilt_libraryto verify unresolved symbols.aligned_alloc(forced via-u) to route large aligned allocations to PSRAM, enable PSRAMUSE_MALLOC, and extend Task WDT timeout.Written for commit 01e9992. Summary will update on new commits.