Repository navigation
Conversation
ImageReader used FFmpeg (avcodec + swscale) to decode JPEG/PNG, convert to RGB24, resize, and encode PNG. Replace it with permissive libraries and match Pillow, which the HF image processors use, exactly: - JPEG: libjpeg-turbo 3.2.0 (IJG/BSD-3-Clause/zlib), libjpeg API with its defaults (accurate integer IDCT, smooth chroma upsampling), i.e. what PIL decodes with. CMYK/YCCK images use Pillow's CMYK->RGB formula, including its inversion of Adobe CMYK. Fatal libjpeg errors longjmp back instead of calling exit(). - PNG: LodePNG (zlib license), vendored in src/include/lodepng; it also replaces FFmpeg's PNG encoder in save_png. - resize_image: a port of Pillow's bicubic resampler (Resample.c) instead of swscale bicubic, so resized pixels equal PIL.Image.resize(BICUBIC). - Reject images over 178,956,970 pixels (Pillow's decompression-bomb limit) instead of attempting multi-GB allocations from tiny headers. libjpeg-turbo refuses add_subdirectory(), so src/cmake/FlmImageCodecs.cmake builds it statically via ExternalProject (pinned URL + SHA-256, libjpeg API only, which also keeps out the libspng/zlib copy TurboJPEG bundles) and defines flm_image_codecs for flm and the src/test harnesses. The harness Makefiles link the system libjpeg; image-only harnesses drop FFmpeg entirely, the audio ones keep it minus swscale. Also drop the unused FFmpeg includes from modeling_gemma3_text.hpp and add both libraries to THIRD_PARTY_NOTICES.txt. FFmpeg itself stays for audio (AudioReader, Whisper) and is removed in a follow-up together with swscale. Verified against Pillow 12.3 (libjpeg-turbo 3.1.4.1): bit-exact decode on 24 images (4:4:4/4:2:2/4:2:0, progressive, optimized, grayscale, Adobe CMYK, 1x1, 2049x1531, every PNG color type, and the repo's real photos and PNGs including the vision sweep test images), bit-exact resize on 27 size pairs (55x down to 1x1, upscales, real and synthetic), exact save_png round trip; truncated/corrupt/unsupported inputs rejected. Co-Authored-By: Claude <[email protected]>
The default <name>-prefix/src/<name>-build layout put libjpeg-turbo's try-compile tlog files at 262 characters on the self-hosted Windows runners, past MAX_PATH, so MSBuild failed its first compiler check (GetOutOfDateItems: item metadata %(FullPath) cannot be applied). Use short working directories under build/ljt instead (-47 chars). Co-Authored-By: Claude <[email protected]>
AudioReader and Whisper's own decoder used FFmpeg (avformat/avcodec +
swresample). Replace both with permissively licensed decoders and a
resampler that reproduces swresample's default filter, so audio reaching
the models stays numerically the same:
- audio_decoder.cpp: format sniffing plus
- WAV/RF64/W64/AIFF, FLAC (native and Ogg) and MP3 via dr_libs
(public domain / MIT-0), vendored at commit dfe83776, which carries the
post-release hardening fixes (e.g. dr_mp3 Xing heap over-read);
- Ogg Vorbis via libvorbis/vorbisfile + libogg (BSD-3-Clause);
- Ogg Opus and WebM/Matroska Opus via libopus (BSD-3-Clause) with a
small Ogg demuxer and a bounds-checked EBML/Matroska parser (unknown-
size elements, all lacing modes, DiscardPadding). Ogg Opus length
follows RFC 7845 (start offset + pre-skip + final granule).
- AAC (.m4a/.mp4/ADTS) is rejected with a clear message listing the
supported formats.
- audio_resampler.cpp: Kaiser-windowed sinc with swresample's defaults
(beta 9, cutoff 0.97, filter_size 32, even tap count, exact rational
phases up to 1024 else interpolated, mirrored edges as in
invert_initial_buffer/resample_flush).
- AudioReader keeps its API and downmix modes; Whisper's two _load_audio
overloads (~500 lines of FFmpeg code) now call AudioReader at 16 kHz
with MEAN downmix, matching the previous swr result (s16 output made swr
average stereo), but without the 16-bit quantization.
Build: src/cmake/FlmAudioCodecs.cmake builds libopus 1.6.1, libogg 1.3.6
and libvorbis 1.3.7 statically via ExternalProject (pinned URLs and
SHA-256, short dirs for Windows MAX_PATH) and compiles audio_decoder.cpp
with -fwrapv. The src/test harnesses link it (CMake) or the system
libraries (Makefiles) and no longer reference FFmpeg at all; phi4_core's
FFmpeg stub headers are gone since no production header includes FFmpeg.
THIRD_PARTY_NOTICES.txt lists the new libraries. FFmpeg itself is now
unused by the code; dropping it from the build and packages follows
separately.
Verified against FFmpeg 8 (PyAV) and libsndfile on 22 files:
- lossless (WAV 8/16/24-bit/float, 6ch, RF64, W64, AIFF, FLAC): bit-exact;
- Ogg Vorbis, Ogg Opus (two muxers), WebM Opus mono/stereo: 133-139 dB SNR
and identical lengths; MP3 ~90 dB (different decoder), real
test_audio.mp3 max abs diff 4.6e-5;
- resampler vs swresample on 11 rate pairs incl. edges: 132-142 dB SNR;
- AAC (M4A, ADTS) rejected.
6,000 mutated/truncated inputs under ASan+UBSan: no crash, leak or UB
(an earlier stb_vorbis-based version leaked and hit UB, hence libvorbis).
Co-Authored-By: Claude <[email protected]>
libvorbis 1.3.7 declares cmake_minimum_required(VERSION 2.8.12), which CMake 4 (CI runners and build containers) rejects: 'Compatibility with CMake < 3.5 has been removed'. Pass CMAKE_POLICY_VERSION_MINIMUM=3.5 to that ExternalProject; older CMake ignores it. Verified with CMake 3.28 and 4.4. Co-Authored-By: Claude <[email protected]>
A self-hosted Windows runner had its connection to downloads.xiph.org reset while fetching opus-1.6.1.tar.gz. Give each ExternalProject a mirror list (GitHub release assets where they exist, downloads.xiph.org, and the OSUOSL mirror); CMake tries them in order and the pinned SHA-256 still guarantees the same tarball. All mirrors verified hash-identical. Co-Authored-By: Claude <[email protected]>
M_PI is not standard C++; MSVC only defines it with _USE_MATH_DEFINES, so audio_resampler.cpp failed to compile on Windows. Use a local constant. Also include <cstdio> explicitly for SEEK_* in audio_decoder.cpp instead of relying on transitive includes. Co-Authored-By: Claude <[email protected]>
The ASR CI on this PR showed gemma4 chat WER worse on every platform (5.05% -> 8-9%). All eval clips are 16 kHz mono MP3, and dr_mp3 by default synthesizes 16-bit PCM and converts it to float, which on these quiet clips is only ~35-45 dB from FFmpeg/mpg123 (they agree with each other at 108-121 dB). Define DR_MP3_FLOAT_OUTPUT: our decode now matches mpg123 at >=127.8 dB and main's FFmpeg input at >=108 dB on all 84 eval clips (identical lengths), and synthetic/real MP3s at 118-124 dB. Fuzzing also found that a WAV header claiming a 1 Hz sample rate makes the 16 kHz resample of a 0.5 MB upload ~8.5 GB. Accept only 4-768 kHz source rates, which caps resampling expansion at 4x. 6,000 mutated inputs under ASan+UBSan are clean, with no slow inputs. Co-Authored-By: Claude <[email protected]>
The previous Whisper decoder asked swresample for s16 output and divided by 32768, so Whisper has always seen 16-bit-quantized audio. Feeding it unquantized floats moved the ASR CI's pooled WER by about +2 points on 3 of 4 platforms (and the opposite way when the input happened to be quantized), i.e. the eval is sensitive to that rounding and this PR is not the place to change it. Quantize exactly as swresample did (lrint(x * 32768), clip to int16, / 32768). On all 84 ASR eval clips the result is identical to main's Whisper input for 99.95% of samples; the rest differ by 1 LSB (MP3 decoder rounding), with identical lengths. Co-Authored-By: Claude <[email protected]>
This branch has not been deployed
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.
What
Decoding (
src/common/audio/audio_decoder.cpp, permissive libraries only):dfe83776, which includes post-release hardening fixes (e.g. a dr_mp3 Xing heap over-read)MediaRecorderoutput)DiscardPadding).m4a/.mp4/ADTS)Ogg Opus output length follows RFC 7845: final granule minus start offset minus pre-skip.
Resampling (
audio_resampler.cpp): a re-implementation of swresample's default filter design, so samples stay where FFmpeg put them. Kaiser-windowed sinc (β 9, cutoff 0.97,filter_size32, rounded up to an even tap count), exact rational phases up to 1024 (otherwise 1024 phases with linear interpolation), and the same edge mirroring asinvert_initial_buffer/resample_flush.AudioReaderkeeps its API and downmix modes. Whisper's two_load_audiooverloads (about 500 lines of FFmpeg code) now callAudioReaderat 16 kHz withMEANdownmix, then quantize to 16-bit exactly as swresample's float→s16 conversion did. Whisper's input is therefore unchanged: on all 84 ASR eval clips, 99.95% of samples are bit-identical tomain's, and the rest differ by one 16-bit step.Input hardening: source sample rates outside 4–768 kHz are rejected. Fuzzing found that a WAV header claiming 1 Hz would make the 16 kHz resample of a 0.5 MB upload about 8.5 GB.
Build:
src/cmake/FlmAudioCodecs.cmakebuilds libopus, libogg and libvorbis statically viaExternalProject, with pinned URLs, SHA-256 hashes, and short directories for Windows' path limit. It also compilesaudio_decoder.cppwith-fwrapv, so the decoders' wrapping arithmetic on corrupt input is defined.src/testharnesses: CMake links the module automatically; the Makefiles use the system-lvorbisfile -lvorbis -logg -lopus. No harness references FFmpeg any more, andphi4_core's FFmpeg stub headers are gone.THIRD_PARTY_NOTICES.txtadds libopus, libogg, libvorbis and dr_libs.Verification
Run locally against FFmpeg 8 (PyAV) and libsndfile 1.2.2, driving the real
AudioReader:test_audio.mp3, all 84 ASR eval clips)Found and fixed during CI
cmake_minimum_required(2.8.12), which CMake 4 rejectsCMAKE_POLICY_VERSION_MINIMUM=3.5M_PIundefined on MSVCDR_MP3_FLOAT_OUTPUTis defined (only ~35–45 dB vs FFmpeg/mpg123)DR_MP3_FLOAT_OUTPUT; gemma back tomain's 5.05%main's s16 quantization for Whisper (see above)Still to check:
.deb, portable, HRX) and Windows (portable, MSI, HRX); first build of the audio ExternalProjects there.asr-testvs main on all four platforms (re-running ona22aecf6).test1.ogg).🤖 Generated with Claude Code