Skip to content

Latest commit

 

History

History
466 lines (351 loc) · 45 KB

File metadata and controls

466 lines (351 loc) · 45 KB

Patch notes

Changes in this fork relative to upstream arthenica/ffmpeg-kit, release by release. For native toolchain/build instructions see BUILD.md; for moving from the old Maven Central artifacts see MIGRATION.md.

Entries below are added only after a release is confirmed live on Maven Central (verified by re-downloading the artifact and inspecting it directly) — never as a "planned" placeholder. See RELEASE-CHECKLIST.md for why.

⚠️ Known issue: drawtext missing from every published build (all lines)

Not a release entry — a correction to what this document promised. Tracked as issue #1.

drawtext is absent from every AAR this fork has published, on all three LTS lines and in every tier, not only basic. Verified by scanning libavfilter.so in the live Maven Central artifacts:

artifact FFmpeg actually built --enable-libharfbuzz drawtext
ffmpeg-kit-full-gpl:8.1.8 n8.1.2 no absent
ffmpeg-kit-full-gpl:7.1.6 n7.1.5 no absent
ffmpeg-kit-full-gpl:6.0.3 n6.1.6 no absent
com.arthenica:ffmpeg-kit-full-gpl:6.0-2 6.0 — present

Cause. Since FFmpeg 6.1, drawtext depends on libharfbuzz, and configure drops a filter silently when its dependency flag is absent. harfbuzz was built and linked all along — libass needs it — but scripts/*/ffmpeg.sh had no harfbuzz) branch, so --enable-libharfbuzz was never passed. The library was in the .so; the filter it enables was not.

Note the 6.0 line: it builds n6.1.6, not 6.0, which is why it is affected too and why upstream's 6.0-2 — a real 6.0 — still has the filter.

Fixed in the build scripts (all three lines, android and apple). The entry recording the released fix will be added here once the artifacts are live and re-verified, per the rule at the top of this file. tools/check-filters.py now reads a built AAR and fails when a tier is missing a filter it promises — every check we had looked at the build inputs, and this one looks at what we shipped.

Also fixed, and more serious: a filtergraph parse error killed the host process. init_complex_filtergraph() left AVFilterInOut *inputs uninitialised and freed it on the failure path, so any unknown filter name in -filter_complex segfaulted in-process instead of returning an error code. That affects every user of -filter_complex, not only drawtext users — a missing filter is simply one way to reach it.

Acceptance run before publishing, 2026-09-25. Everything above checks what the artifact contains. None of it encodes a second of video. A library can register 483 filters and still write empty files, so each of the nine 8.1 coordinates was run through 21 real operations on a device, twice -- once on what is published today, once on 8.1.9 -- and the two compared:

tier published 8.1.9 what changed
ffmpeg 15 OK / 6 n-a 15 / 6 nothing
ffmpeg-kit-min 14 / 7 14 / 7 nothing
ffmpeg-kit-min-gpl 16 / 5 16 / 5 nothing
ffmpeg-kit-https 14 / 7 14 / 7 nothing
ffmpeg-kit-https-gpl 16 / 5 16 / 5 nothing
ffmpeg-kit-audio 16 / 5 16 / 5 nothing
ffmpeg-kit-video 16 / 5 17 / 4 drawtext now works
ffmpeg-kit-full 18 / 3 19 / 2 drawtext now works
ffmpeg-kit-full-gpl 20 / 1 21 / 0 drawtext now works

189 checks each side, zero failures on either. The operations are the ones an app actually performs: encode H.264/H.265/VP9/MP3/Opus, remux without re-encoding, scale + crop + hflip with the width read back after the crop, overlay, burn in ASS subtitles, volume and atempo, pull a thumbnail at 1.5 s, write a title and read it back, concatenate and check the duration doubled, and -- after two deliberate errors -- prove the process still answers.

Each check reads the file produced, not the return code. An ffmpeg that returns 0 while writing 0 bytes is exactly the kind of success that misleads.

⚠️ Three of those checks were wrong when first written, and the shape is the one this whole release is about. They decided "this tier never had that codec" by pattern- matching ffmpeg's error text, which is guessing. The worst was the survival check: it wrote a PNG, so on a tier without the PNG encoder the check meant to detect a crash failed for an unrelated reason and read exactly like a crash. It now uses -f null -, which needs no encoder at all. The suite asks -encoders, -filters, -muxers and -demuxers what the build can do, and only requires what the build claims. Fixing that turned 8 false failures into 0 and moved no real verdict.

Which pipeline produces the reported artifact. Two families of workflow build this repository, and they are easy to confuse:

workflow produces delivery
build-*-free-*.yml dev.ffmpegkit-maintained:ffmpeg-kit-* Maven Central, after manual verification
build.yml, build-gpl.yml, build-NN-{full,gpl}.yml the Gumroad tiers private cache repo, then Gumroad

Issue #1 was filed against Maven Central artifacts, so the fix the reporter needs comes out of the free workflows. Both families were affected and both are fixed, but the order matters and it is the Maven side that is the reporter's.

⚠️ Until 2026-09-24 the free workflows verified 16 KB alignment and the absence of paid-tier libraries, and nothing about what the build actually contained. They now run tools/check-filters.py --pin against the .aar they are about to hand over.

The crash fix, and how it is checked now. The second defect was AVFilterInOut *inputs, *outputs, *cur; -- no initialiser -- freed at the fail: label of init_complex_filtergraph(). graph_parse() writes *inputs only in its last call, avfilter_graph_segment_apply; an unknown filter fails before that and returns without touching it, so the error path freed whatever the stack held.

Two ways of witnessing it were tried, and both failed for reasons worth recording:

  • The compiler cannot see it. Measured 2026-09-24 with the NDK's own clang on a reduction of the real function: -Wall -Wextra is silent, and so is -Wconditional-uninitialized, which exists for this exact shape. The variable's address is handed to a callee, so clang assumes the callee initialises it. That is why this survived -Werror builds for the life of the fork.
  • The device cannot see it either. Freeing an uninitialised pointer is undefined behaviour, so a crash depends on what the previous call left on the stack. On a Pixel 7 Pro, 180 failing parses of three shapes -- unknown filter first, unknown filter after six valid ones, unknown option on a valid filter -- against the unfixed binary crashed nothing. The value there happens to be null. The reporter saw it on Android 12.

⚠️ So neither green line means anything for this defect, and reading either as a verdict would be a mistake. What checks it is tools/check-uninit-free.py, run by CI: it flags a local pointer freed at a cleanup label when the call that was meant to write it jumps to that same label on failure. Witnessed both ways -- on the pre-fix file it names inputs at line 493 and the free at 524, which is the reporter's tombstone exactly; on the 39 vendored fftools files as they stand it passes.

The rule is narrow on purpose. An earlier version also flagged enc_stats_init's val, which is written by unescape(&val, ...) whose failure does a plain return and never reaches the label. Same address-passing shape, not a defect. A check that flags what is fine is how a check gets switched off.

The 6.0 line is two different builds, and only one of them is affected. Measured 2026-09-24, on the device, by asking ffmpeg's own filter table (-filters) rather than scanning strings:

artifact FFmpeg it reports drawtext in the filter table
Maven Central ffmpeg-kit-full-gpl:6.0.3 n6.1.6 no (482 filters listed)
the Gumroad full-gpl 6.0 package on file n6.0 yes, and it renders

So the 6.0 Maven artifacts carry n6.1.6, where drawtext needs harfbuzz and was dropped; the Gumroad 6.0 package on file is a real 6.0, from before that switch, where drawtext has no such dependency and works. ⚠️ The Gumroad zip currently on sale has not been checked against this -- worth confirming it is the same build before assuming 6.0 buyers are unaffected.

-filters is the authority here, not tools/check-filters.py. That tool counts byte strings in libavfilter.so, and for 6.0 its "present" verdict rested on a single occurrence of the word. It happened to be right -- the name string is there when the filter is compiled and gone when it is not -- but one string is a thin thing to publish a release on, and the symbol names it would rather read are stripped from a release .so. The device harness asks the registration table directly.

Measured on a device, 2026-09-24. Pixel 7 Pro, Android 16, arm64, running the faulty June AAR (ffmpeg-kit-full-gpl-8.1, FFmpeg n8.1.2) so the harness could be checked against a build known to be broken:

check result on the faulty AAR
drawtext renders a frame fails — No such filter: 'drawtext'
a bad -filter_complex returns an error passes
-filter_complex with drawtext, the reporter's exact shape returns an error
the process still answers afterwards passes

The first line reproduces the report exactly. The last line does not prove the crash fix, and should not be read as doing so: the defect is an uninitialised AVFilterInOut* freed on the error path, so what gets freed is whatever the stack held, and whether it segfaults depends on the preceding call, the device and the Android version. On this device it happens to be null. The reporter saw it on Android 12.

A check that cannot see the defect returns a verdict of success for that defect. The evidence for the second fix is the code and the reporter's tombstone, which names avfilter_inout_free directly under init_complex_filtergraph; not a green line here.

Full / Full GPL Maven aliases (6.0.3 / 7.1.6 / 8.1.7) — 2026-07-12

Completes the 8-name Arthenica tier matrix on all three LTS lines: ffmpeg-kit-full and ffmpeg-kit-full-gpl are now live on Maven Central for 6.0, 7.1, and 8.1 (24/24 artifacts — min, min-gpl, https, https-gpl, audio, video, full, full-gpl × 3 lines).

  • Fix: full/full-gpl builds deadlocked silently forever. freetype (needed by fontconfig/harfbuzz/libass, part of full's video scope) depends on libpng. Disabling tesseract (kept Pro-exclusive on every Free tier) cascades a set_library "libpng" 0 in scripts/function.sh, since tesseract also depends on libpng — silently undoing --full's earlier enable of libpng even though freetype still needed it. freetype then permanently failed its dependency check in main-android.sh, and the build's own while loop spun forever waiting for it — no error, no timeout, just total silence. Fixed in android.sh (all 3 lines): libpng is now re-asserted after the disable pass whenever freetype is still enabled.
  • CI: all 32 build-*.yml workflows had their diagnostic log steps (build.log, config.log) gated on if: failure() only — a cancelled run (exactly what a silent deadlock requires) never triggered them, leaving no trace to debug from. Now also runs on cancelled(). Also removed the dead per-build checkpoint push loop (permanently broken since an org-level workflow-permissions lockdown, see below) that was wasting CI time and I/O on every tier.
  • Validated: CI build (all 3 lines, both variants) + real-device instrumented test (Android emulator, Pixel 7 Pro AVD — FFmpeg encode + ffprobe via JNI, no crash) + re-downloaded the live Maven Central artifact and confirmed the .so files match, before this entry was written.
  • Note: the org's default_workflow_permissions is intentionally locked to read-only, so the per-build checkpoint-resume mechanism used earlier in this project can no longer work — any future build failure requires a full rebuild from scratch, not a resume.

v7.1.6-lts-android — 2026-07-11

Security + quality release for the Free tier (Basic/Full/Full GPL to follow via Gumroad).

  • Fix: JNI memory leak in registerNewNativeFFmpegPipe — inherited from the original FFmpegKit and never fixed upstream. GetStringUTFChars was never paired with ReleaseStringUTFChars, leaking one JVM string buffer per FFmpeg pipe created. Fixed in our JNI bridge on 2026-06-24; this is the first 7.1 release to actually ship it — confirmed absent from 7.1.5 by disassembly of the live Maven Central artifact.
  • Removed: SANM decoder — CVE-2025-59730/59734 (heap overflow), never backported upstream past FFmpeg 8.0, no realistic Android use case. Disabled via --disable-decoder=sanm.
  • MagicYUV (CVE-2026-8461), MPEG-DASH (CVE-2025-59728), EXR (CVE-2025-59733/31/32), and the H.264/HEVC RTP packetizer (CVE-2026-30754) were already fixed in n7.1.5's FFmpeg source — verified by disassembly, no other CVE exposure on this line.
  • Validated: CI build + disassembly (JNI fix, sanm inactive) + real-device runtime test (Pixel 7 Pro) before publishing.

v6.0.3-lts-android — 2026-07-11

Security release for the Free tier (Basic/Full/Full GPL to follow via Gumroad).

  • Rebased FFmpeg source from n6.0 to n6.1.6 — the last point release upstream actually continues to patch (the strict 6.0.x branch was abandoned after n6.0.1). Closes four CVEs, all verified present in n6.1.6's source by direct inspection (not by trusting ffmpeg.org/security.html, which lags behind actual branch backports):
    • CVE-2026-8461 (CVSS 8.8) — MagicYUV decoder heap out-of-bounds write, publicly discussed PoC. Discovered and reported by Ori Hollander, JFrog Vulnerability Research team.
    • CVE-2025-59728 (CVSS 8.7) — MPEG-DASH demuxer out-of-bounds NUL-byte write.
    • CVE-2025-59733 / 59732 / 59731 (CVSS 7.1–7.8) — OpenEXR dwa_uncompress heap overflow.
    • CVE-2026-30754 — H.264/HEVC RTP packetizer bounds issue.
  • Removed: SANM decoder — CVE-2025-59730/59734, never backported upstream past FFmpeg 8.0, no realistic Android use case.
  • Fix: JNI memory leak in registerNewNativeFFmpegPipe — inherited from the original FFmpegKit and never fixed upstream, present in every 6.0.x AAR published to date. Confirmed by disassembly of the live 6.0.1 Maven Central artifact (2-call leaky pattern, no ReleaseStringUTFChars) vs. this release (3-call fixed pattern).
  • Two build-porting fixes needed for the n6.1.6 rebase (both previously solved during the 7.1 port): libavutil/x86/emms.h moved to libavutil/emms.h; missing #include <string.h> in ffmpegkit.c/ffprobekit.c (latent bug masked by FFmpeg 6.0's header layout, exposed by 6.1's refactor).
  • Not applicable: CVE-2026-30999 — affects the standalone tools/zmqsend utility, never compiled as part of ffmpeg-kit.
  • Validated: CI build on both Free and Full (zlib-enabled, to exercise the EXR DWA fix path) tiers + disassembly + real-device runtime test (Pixel 7 Pro) before publishing.
  • com.arthenica.ffmpegkit API surface unchanged on both 6.0.3 and 7.1.6 — drop-in upgrade.

v8.1.7-lts-android — 2026-06-24

Critical fix — audio pipeline (all four 8.1 tiers).

  • Fix (C-CRIT): all_channel_counts crash in fftools_ffmpeg_filter.c — av_opt_set_int(abuffersink, "all_channel_counts", 1) was called during filter graph initialization. FFmpeg 8.x removed this option from abuffersink's AVOption table; the call returned an error that immediately aborted the filter graph, causing every audio operation (even a trivial AAC→PCM passthrough) to fail with:
    Option 'all_channel_counts' is not a runtime option
    Error reinitializing filters! Conversion failed!
    
    Fix: removed the av_opt_set_int call. The new AVChannelLayout API in FFmpeg 8.x handles flexible channel counts without this option. Affects all four 8.1 tiers; upgrade from any 8.1.x is strongly recommended.

v8.1.6-lts-android (Full / Full GPL) — 2026-06-24

WhisperKit JNI quality fixes for the Full and Full GPL tiers. No change for Free or Basic tiers.

  • Fix (C-1): use-after-free in nativeFullTranscribe — null-check added for audio_data and language before GetFloatArrayElements / GetStringUTFChars; returns -1 with a log instead of crashing on null input.
  • Fix (M-1): nativeGetSystemInfo null guard — whisper_print_system_info() return value now guarded against NULL before passing to NewStringUTF.
  • Fix (M-2): dead code removed — cs_to_srt_time() helper function was defined but never called; removed.
  • CI (all 12 workflows): removed dead actions/cache/restore@v4 steps that were always cache misses (real restore uses git-based checkpoint branches).

Breaking — FFmpeg 8.x audio resampling and channel conversion: FFmpeg 8.x removed all_channel_counts as a runtime option. Both -ac N and -ar N (or any use of the aresample filter, explicit or implicit) fail with:

Option 'all_channel_counts' is not a runtime option
Error reinitializing filters! Conversion failed!

Workaround: extract raw PCM without any -af/-ar/-ac, then handle downmix and resampling in Java:

// Extract raw PCM as-is (no channel/rate conversion in FFmpeg)
FFmpegKit.execute("-i input.mp4 -vn -c:a pcm_s16le -y output.wav");

// Parse WAV header (offset 22 = numChannels s16le, offset 24 = sampleRate i32le)
// Downmix channels to mono, then linear-interpolation resample to 16000 Hz

This is required for Whisper.cpp pipeline compatibility.


v7.1.6-lts-android — 2026-06-24

Quality fix release for all four tiers of the 7.1 LTS line. Includes the JNI leak fix and version string corrections from the 8.1.5 Free release.

  • Fix (C-1): JNI memory leak in registerNewNativeFFmpegPipe — GetStringUTFChars result was never released, leaking one JVM string buffer per FFmpeg pipe creation. Fixed by saving the return value, calling mkfifo, releasing the string, then returning.
  • Fix (C-2): getVersion() returned "6.0-lts" at runtime — FFMPEG_KIT_VERSION was hardcoded to "6.0" in ffmpegkit.h. Updated to "7.1".
  • Fix (M-1): NativeLoader.loadVersion() test-mode fallback — hardcoded "6.0" updated to "7.1" so unit tests see the correct version string.
  • CI: removed dead actions/cache/restore@v4 steps in all four 7.1 workflows.

v6.0.3-lts-android — 2026-06-24

Quality fix release for all four tiers of the 6.0 LTS line. Supersedes 6.0.2 (which was published to Maven Central before the JNI fix below).

  • Fix (C-1): JNI memory leak in registerNewNativeFFmpegPipe — GetStringUTFChars result was never released, leaking one JVM string buffer per FFmpeg pipe creation. Fixed by saving the return value, calling mkfifo, releasing the string, then returning. Maven Central artifact: dev.ffmpegkit-maintained:ffmpeg-kit-free:6.0.3.
  • CI: removed dead actions/cache/restore@v4 steps in all four 6.0 workflows.

v8.1.5-lts-android — 2026-06-24

Free tier: bug-fix release for ffmpeg-kit-free-81. No native codec change — only JNI correctness fixes.

  • Fix (C-1): getVersion() returned "6.0-lts" at runtime — FFMPEG_KIT_VERSION was hardcoded to "6.0" in ffmpegkit.h. Updated to "8.1". All three API paths now agree: getVersion() → "8.1-lts", Maven coordinates 8.1.5, build.gradle versionName "8.1".
  • Fix (C-2): JNI memory leak in registerNewNativeFFmpegPipe — GetStringUTFChars result was never released, leaking one JVM string buffer per FFmpeg pipe creation. Fixed by saving the mkfifo return value, releasing the string, then returning.
  • Fix (M-4): NativeLoader.loadVersion() test-mode fallback — hardcoded "6.0" updated to "8.1" so unit tests see the correct version string.
  • Fix: use-after-free in nativeInitContext (WhisperKit, Full/Full GPL only) — the failure log LOGE("Failed to load Whisper model from %s", path) was emitted after ReleaseStringUTFChars, making path a dangling pointer. Log is now emitted before Release.
  • Fix: stale comment in build-81-free.yml — copy-paste from the 7.1 workflow said "other three 7.1 tiers"; corrected to 8.1.

Basic/Full/Full GPL: no change for these tiers in this version bump.


v8.1.5-lts-android (Full / Full GPL) — 2026-06-23

First release with a working Whisper JNI bridge for the Full and Full GPL tiers. No code change for the Free or Basic tiers.

  • Fix: Whisper was never actually built — main-android.sh loop capped at index 50; Whisper is at index 92. Extended to {1..93}; added whisper) case in the dependency switch. Extended android.sh flags loop from {0..61} to {0..92}.
  • New: libwhisperkit.so — JNI shared library built conditionally (only when libwhisper.a is present in the prebuilt tree). Added in android/jni/Android.mk; links all ggml static archives with --start-group/--end-group.
  • New: whisperkitjni.c — C JNI bridge wrapping the Whisper.cpp v1.7.5 C API: nativeInitContext, nativeFreeContext, nativeFullTranscribe, nativeGetSegmentCount/Text/T0/T1, nativeGetSystemInfo.
  • New: WhisperKit.java — public Java API: createFromFile(), transcribe(), transcribeToSrt(), translate(), translateToSrt(), low-level segment access. Graceful degradation on Free/Basic tiers (loads the library conditionally; createFromFile() throws a descriptive IOException when the native library is absent).
  • New: TranslationProvider — functional interface for plugging in any external translation service (DeepL, LibreTranslate, Google Translate, Azure, etc.). Powers WhisperKit.transcribeAndTranslate() and transcribeToSrtAndTranslate(), which translate each SRT segment individually to keep timestamps in sync.
  • New: DeepLTranslationProvider — batteries-included implementation for the DeepL REST API. Auto-selects the Free vs. paid endpoint from the API key format. Uses HttpsURLConnection — no extra dependencies.
  • New: LibreTranslateProvider — batteries-included implementation for any LibreTranslate-compatible server (self-hosted or public). Configurable server URL and optional API key. Uses HttpsURLConnection — no extra dependencies.
  • New: docs/WHISPERKIT.md — full developer documentation: model download, PCM extraction via FFmpegKit, all API methods, DeepL/LibreTranslate quickstarts, end-to-end example, thread-safety and performance notes.

v8.1.4-lts-android — 2026-06-23

Rebuild of all four tiers with NDK r27c (27.2.12479018 — current NDK LTS), the first 8.1 release to use the newer toolchain. Source identical to 8.1.2. Maven artifact for the Free tier: dev.ffmpegkit-maintained:ffmpeg-kit-free-81:8.1.4.

  • NDK r26c → r27c for the entire 8.1 line (all 4 tiers). The 6.0 and 7.1 lines remain on r26c.
  • Clang from NDK r27c (clang-r510928) replaces the r26c compiler across all 8.1 native builds.

v8.1.3-lts-android — 2026-06-23

Accidental release: version bump targeting r27c but the CI workflow download URL was not yet updated at tag time, resulting in an r26c build. The Maven Central artifact ffmpeg-kit-free-81:8.1.3 exists but is functionally identical to 8.1.2 (same NDK r26c). Superseded by 8.1.4. Paid tiers were never built as 8.1.3.

v8.1.2-lts-android — 2026-06-23

A third build tree, android-8.1-lts/, targeting FFmpeg n8.1.2 (8.1 "Hoare" line, latest patch as of 2026-06-17). Same four-tier structure (Free/Basic/Full/Full GPL), same NDK r26c, same 16 KB page alignment. Free tier Maven artifact: dev.ffmpegkit-maintained:ffmpeg-kit-free-81:8.1.2.

New in this line vs 7.1:

  • FFmpeg n8.1.2 — latest stable in the 8.x line
  • Whisper.cpp v1.7.5 (ggml-org/whisper.cpp) integrated as library index 92 in the Full and Full GPL tiers — new scripts/android/whisper.sh build script, cmake-based, static, with WHISPER_BUILD_TESTS/EXAMPLES/SERVER=OFF
  • Vulkan video codec acceleration improvements (H.264, AV1, ProRes) from upstream 8.x

Status: Free tier published on Maven Central as dev.ffmpegkit-maintained:ffmpeg-kit-free-81:8.1.2. Paid tiers (Basic/Full/Full GPL) built and available on Jokobee.

FFmpeg 8.x breaking API changes found during port — all documented in doc/APIchanges in the FFmpeg source tree. Changes were required in our copies of fftools_ffmpeg.c and fftools_ffprobe.c (these are frozen snapshots of FFmpeg's internal CLI tool source, carried in the repo since 6.0 and only updated when a new FFmpeg version removes the fields they use):

  • --disable-postproc configure flag removed — libpostproc was removed from FFmpeg 8.0 entirely. Dropping the flag from scripts/android/ffmpeg.sh (and the unused linux/ and apple/ variants for consistency).

  • AVFrame.key_frame removed (deprecated since FFmpeg 6.1) — use !!(frame->flags & AV_FRAME_FLAG_KEY). Affected: fftools_ffmpeg.c (4 sites), fftools_ffprobe.c (1 site).

  • AVFrame.interlaced_frame removed (deprecated since FFmpeg 6.1) — use !!(frame->flags & AV_FRAME_FLAG_INTERLACED). Affected: fftools_ffmpeg.c (1 site), fftools_ffprobe.c (1 site).

  • AVFrame.top_field_first removed (deprecated since FFmpeg 6.1) — reading: !!(frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST); writing: frame->flags |= AV_FRAME_FLAG_TOP_FIELD_FIRST or &= ~AV_FRAME_FLAG_TOP_FIELD_FIRST. Affected: fftools_ffmpeg.c (3 read sites, 1 write site), fftools_ffprobe.c (1 read site).

  • AVCodecContext.ticks_per_frame removed — was used in two duration-calculation expressions. In the general case (non-interlaced), effectively cancelled out (same value in numerator and denominator), replaced with 1. Affected: fftools_ffmpeg.c (4 sites in 2 identical blocks).

  • AVStream.nb_side_data / AVStream.side_data removed — stream-level packet side data moved to AVStream.codecpar->coded_side_data / nb_coded_side_data. av_stream_new_side_data() also removed, replaced by av_packet_side_data_new(&codecpar->coded_side_data, &codecpar->nb_coded_side_data, type, size, 0) (available since FFmpeg 7.0). Affected: fftools_ffmpeg.c (3 blocks, 5 sites total), fftools_ffprobe.c (1 block), fftools_ffmpeg_demux.c (1 site).

  • av_stream_get_side_data() removed — replaced by av_packet_side_data_get(codecpar->coded_side_data, nb_coded_side_data, type) which returns a const AVPacketSideData * (access .data for the payload). Affected: fftools_ffmpeg_filter.c (1 site — display matrix auto-rotation).

  • AV_CODEC_CAP_SUBFRAMES removed — this codec capability flag was eliminated in FFmpeg 8.0. The only use was a diagnostic log line in fftools_opt_common.c; the entire if block was removed. Affected: fftools_opt_common.c (1 site).

  • AVFilter.process_command member removed from public struct — this function pointer was part of the internal AVFilter struct exposed publicly; it was removed in FFmpeg 8.x as part of filter API cleanup. The only use was a ternary filter->process_command ? 'C' : '.' in a diagnostic listing in fftools_opt_common.c. Replaced with literal 0 (always outputs '.'). Affected: fftools_opt_common.c (1 site).

  • AVFrame.pkt_pos / AVFrame.pkt_size removed — were used in ffprobe's frame output to print the originating packet's byte position and size. No equivalent exists on AVFrame in FFmpeg 8.x (this information is now only available at demux time on AVPacket). Replaced with print_str_opt("pkt_pos", "N/A") / print_str_opt("pkt_size", "N/A") — these fields will show as N/A in ffprobe output for frames decoded with this build. Affected: fftools_ffprobe.c (2 sites).

  • FF_PROFILE_UNKNOWN renamed to AV_PROFILE_UNKNOWN — part of a broader FF_PROFILE_* → AV_PROFILE_* rename in FFmpeg 8.0. Affected: fftools_ffprobe.c (1 site).

  • AVHDRVividColorToneMappingParams three-spline fields restructured — the individual spline parameters (three_Spline_TH_mode, three_Spline_TH_enable_MB, three_Spline_TH_enable, three_Spline_TH_Delta1, three_Spline_TH_Delta2, three_Spline_enable_Strength) were direct fields on the parent struct in FFmpeg 7.x. In FFmpeg 8.x they are now per-element fields (th_mode, th_enable_mb, th_enable, th_delta1, th_delta2, enable_strength) inside the new AVHDRVivid3SplineParams sub-struct, accessible via tm_params->three_spline[j] (note also: lowercase s in the array field name). Affected: fftools_ffprobe.c (1 block).

Other port fixes (non-API):

  • --disable-postproc configure abort — this was the first build failure: FFmpeg configure exits immediately with "Unknown option" for any unrecognised flag, so this single stale flag blocked the entire FFmpeg compile step.
  • src/main/cpp/ directory missing from git — the JNI source tree (ffmpegkit.c, fftools_*.c/h, AndroidManifest.xml, all Java sources) was not included when the android-8.1-lts/ directory was initially set up. Copied from android-7.1-lts/, which carries the same codebase.
  • All .sh scripts missing execute bit — the entire android-8.1-lts/scripts/ tree was added to git with mode 100644 instead of 100755, causing android.sh to fail with "Permission denied" (exit 126) before any build work started. Fixed with git update-index --chmod=+x on all .sh files.
  • android/gradlew missing execute bit — same root cause: android-8.1-lts/android/gradlew was tracked at mode 100644 instead of 100755, causing ./gradlew: Permission denied at the Gradle AAR assembly step (after ndk-build had already succeeded). Fixed with git update-index --chmod=+x android-8.1-lts/android/gradlew.

v7.1.5-lts-android — 2026-06-23

A second build tree, android-7.1-lts/, coexists with the original android-6.0-lts/. Same four tiers (Free/Basic/Full/Full GPL), same NDK r26c, same 16 KB page size alignment — FFmpeg updated to n7.1.5. Free tier published on Maven Central as dev.ffmpegkit-maintained:ffmpeg-kit-free-71:7.1.5; paid tiers available on Jokobee.

Port findings — all four tiers compiled cleanly against FFmpeg n7.1.5 with only two generic fixes needed:

  • All four tiers (Free/Basic/Full/Full GPL) compile cleanly against FFmpeg n7.1.5, with only two generic fixes needed, neither library-specific:
    • scripts/android/ffmpeg.sh: emms.h moved from libavutil/x86/emms.h to libavutil/emms.h upstream between 6.0 and 7.1 — updated the path our script manually copies it from/to.
    • ffmpegkit.c / ffprobekit.c (FFmpegKit's own JNI bridge files, not copied from upstream FFmpeg): both call strlen/strcpy without #include <string.h> — a latent bug that happened to compile under 6.0 because some FFmpeg header transitively pulled it in; that path is gone in 7.1, exposing it. Added the missing include directly.
  • An initial audit (before any of this was attempted) predicted a large, risky JNI wrapper rewrite would be needed, because upstream's fftools/ffmpeg.c shrank from 4196 to 1017 lines between 6.0 and 7.1 with logic moved into new ffmpeg_dec.c/ffmpeg_enc.c/ffmpeg_sched.c files. That turned out not to apply to us: those new files are internal to upstream's own standalone ffmpeg CLI binary, never compiled as a library — FFmpegKit's wrapper has always had its own independent fork of the transcode logic (fftools_ffmpeg.c, unchanged in this fork since 6.0) that only depends on the real public API (libavcodec/libavformat/libavutil), which stayed compatible. The bitfield-truncation warning patch (-Wno-error=single-bit-bitfield-constant-conversion in Android.mk) is also still needed for the same reason — our copy of fftools_ffmpeg_mux_init.c is unchanged, even though upstream's own version fixed it (int → unsigned bitfields) in the part of the file we don't carry forward.
  • Runtime-verified, not just compile-checked: a temporary local test app (test-app/, gitignored, not part of the repo) confirmed on a real Pixel 7 Pro (arm64-v8a) that the Free tier's ported .aar actually works — FFmpegKitConfig.getFFmpegVersion() reports n7.1.5, FFmpegKit.execute("-version") runs and returns success, zero UnsatisfiedLinkError.
  • An attempt to automate this same check in CI via an instrumented test (android-7.1-lts/.../src/androidTest/.../SmokeTest.java + .github/workflows/port-71-smoketest.yml) hit a hard platform wall: the modern Android Emulator categorically refuses to boot an arm64-v8a system image on an x86_64 host — FATAL: Avd's CPU Architecture 'arm64' is not supported by the QEMU2 emulator on x86_64 host — confirmed identically on both a GitHub Actions runner and a local Windows machine. Switching to a genuine ARM64 GitHub-hosted runner (ubuntu-24.04-arm, free for public repos, matching host/guest architecture) avoids that specific error but hit a second blocker first: that image doesn't auto-configure ANDROID_HOME/ANDROID_SDK_ROOT at all (tracked upstream: actions/runner-images#11460). Not pursued further since the real-device path above already gave a conclusive answer; both diagnostic files are left in the repo in case the CI-emulator path is worth revisiting later.

Unreleased (source only — no tag/release yet)

Closes the two "known gaps" from v6.0.0-lts-android below. Not yet shipped as a downloadable .aar — push a new v* tag to trigger the CI build and cut a release once these are ready to ship.

Added

  • Real CI build: .github/workflows/build.yml now actually runs ./android.sh --full ... on ubuntu-24.04 (NDK r26c, JDK 17, arm64-v8a only) instead of the previous placeholder, triggered on v* tags. Produces a downloadable ffmpeg-kit-full-arm64-v8a artifact.
  • CI step that enforces 16 KB ELF segment alignment on every built .so and fails the build if any library isn't aligned.
  • -Wl,-z,max-page-size=16384 / -Wl,-z,common-page-size=16384 added to the native linker flags (scripts/function-android.sh get_ldflags() for the FFmpeg libraries, and a new android/jni/Application.mk for libffmpegkit.so).
  • Three more build tiers alongside the original full build (renamed Full, build.yml), each its own workflow/cache/ci-cache-* branch so none of them ever share a cache namespace or mix artifacts:
    • Full GPL (build-gpl.yml, $39 on Jokobee): --full --enable-gpl, adds x264/x265/xvidcore/libvidstab/rubberband. Changes the AAR's license to GPL-3.0 — confirmed via a dedicated CI step that checks the GPLv3 text both at android/ffmpeg-kit-android-lib/src/main/res/raw/license.txt and inside the built .aar's res/raw/license.txt.
    • Basic (build-basic.yml, $19 on Jokobee): --full minus kvazaar (H.265 encode) and four niche blocks (OCR: tesseract+leptonica; subtitle/text rendering: libass+harfbuzz+freetype+fontconfig+fribidi, which also drops FFmpeg's drawtext filter — but see the note below: drawtext has in fact been missing from every tier, including full and full-gpl; srt; chromaprint). openh264 (H.264 encode) is deliberately kept — it's the single most common use case, an earlier draft dropped it too and that made the tier much less useful.
    • Free (build-free.yml, Maven Central, no charge): software-only (no --enable-android-media-codec), explicit --enable-libaom --enable-dav1d --enable-libvpx --enable-opus --enable-libvorbis --enable-speex instead of --full-minus-something, since it's a much smaller set. No H.264/H.265 encode, no TLS, no images beyond what FFmpeg needs internally, no subtitles/OCR/SRT/fingerprinting.
    • See README § Available tiers for the full per-tier feature table shown to customers.
  • Maven Central publishing for the Free tier: com.vanniktech.maven.publish (pinned to 0.34.0 — 0.35+ requires AGP 8.13+, all four tiers are pinned to AGP 8.6.0) applied to ffmpeg-kit-android-lib/build.gradle, coordinates dev.ffmpegkit-maintained:ffmpeg-kit-free, full POM metadata for Central Portal validation. build-free.yml gained a Publish to Maven Central step gated on startsWith(github.ref, 'refs/tags/') (not workflow_dispatch) since a Central Portal release can't be un-published. Credentials via OSSRH_USERNAME/OSSRH_PASSWORD (Sonatype user token) and GPG_PRIVATE_KEY/GPG_PASSPHRASE (in-memory signing key) repo secrets. Namespace dev.ffmpegkit-maintained verified directly on the Central Portal (not the io.github.<org> auto-verification path, which only covers personal GitHub usernames, not organizations).

Changed

  • compileSdk/targetSdk bumped from 33 to 35 (Android 15) in android/ffmpeg-kit-android-lib/build.gradle.
  • Android Gradle Plugin bumped 8.1.0 → 8.6.0, Gradle wrapper 8.2.1 → 8.7 (required for clean compileSdk 35 support; verified locally with ./gradlew :ffmpeg-kit-android-lib:assembleRelease).
  • ndkVersion field in the Gradle module corrected to 26.2.11394342 (r26c) to match what's actually used; this field doesn't drive the native build itself (that's android.sh/ndk-build), it was just stale.
  • Removed the manual android.publishing.singleVariant('release') { withJavadocJar(); withSourcesJar() } block from ffmpeg-kit-android-lib/build.gradle: com.vanniktech.maven.publish auto-configures the exact same thing for Android libraries, and having both threw "singleVariant publishing DSL used multiple times ... is not allowed" at configuration time. ./gradlew :ffmpeg-kit-android-lib:assembleRelease verified to still produce an identical AAR with the plugin handling it alone.
  • Groovy gotcha while wiring up the plugin: publishToMavenCentral(automaticRelease: true) fails ("Could not find method ... for arguments [{automaticRelease=true}]") because Groovy's named-argument-via-map sugar only applies to methods that accept a Map, not a plain Boolean parameter — the correct call is the positional publishToMavenCentral(true).

CI build fixes (getting the from-source build actually green on NDK r26c / Ubuntu 24.04)

Each of these surfaced one at a time as the CI build progressed further with each fix:

  • yes | sdkmanager --licenses failed under GitHub Actions' default bash -e -o pipefail: yes gets a broken-pipe exit once sdkmanager stops reading, which pipefail turned into a step failure even though licenses were accepted fine. Wrapped with set +o pipefail / set -o pipefail.
  • Missing apt prerequisites (groff broke libiconv's man-page build target; restored the rest of upstream's proven list: doxygen cmake autogen autopoint gtk-doc-tools libtasn1-bin) and an incomplete arch restriction (--disable-arm-v7a alone still built the neon variant; added --disable-arm-v7a-neon).
  • NDK r24+ clang turned -Wimplicit-function-declaration (libaom neon intrinsics) and -Wincompatible-function-pointer-types (SDL's GLES2 typedefs) into hard errors by default. Downgraded both to warnings centrally in get_common_cflags().
  • cpu_features v0.8.0's CMakeLists.txt declares an old cmake_minimum_required that current CMake refuses outright ("Compatibility with CMake < 3.5 has been removed"). Added -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to the shared android_ndk_cmake() helper and to libaom's separate cmake invocation.
  • That same policy fix didn't cover cpu_features' own vendored googletest sub-build (a separate nested cmake invocation with the identical issue). Disabled it outright with -DBUILD_TESTING=OFF instead of chasing the flag through a nested process.
  • get_common_linked_libraries() included -L.../toolchains/llvm/prebuilt/linux-x86_64/lib — the NDK's host-side lib dir, not a target-arch one. libvpx's configure does a -static link probe as part of its toolchain capability check, and ld.lld picked up the host's libc++abi.a there and rejected it ("is incompatible with aarch64linux" → "Toolchain is unable to link executables"). Removed that path; the target toolchain lib dir + target sysroot already cover everything needed for aarch64 linking.
  • fftools_ffmpeg_mux_init.c assigns the literal 1 to several 1-bit signed bitfields (truncates to -1, harmless but now clang-flagged) during the always-run ndk-build step for libffmpegkit.so itself — found via an isolated --enable-libass-only test build that reaches that same ndk-build step in ~10 minutes instead of waiting on the other ~25 libraries --full enables. Fixed with -Wno-error=single-bit-bitfield-constant-conversion in MY_CFLAGS in android/jni/Android.mk.
  • The actual cause of a --full attempt hanging for ~6h until GitHub's own hard timeout killed it (conclusion: cancelled, no error — confirmed via direct API check on run_started_at/updated_at; the bitfield fix above was real but unrelated to this): --full enables gnutls, lame, and libass, each independently cascading set_virtual_library "libiconv" 1. --disable-lib-gnutls then calls set_library "gnutls" 0, which cascades set_virtual_library "libiconv" 0 too — unconditionally wiping what lame/libass had just enabled. libiconv's "OK" flag then never gets set, and main-android.sh's dependency-resolution loop (which only exits once every enabled library reaches "completed") spins forever re-printing INFO: Skipping <lib>, dependencies built=... for every library still waiting on it (fontconfig, lame, libass, libxml2) — confirmed via the actual build.log: 198,019 such lines (~6,600 repeats of the full pass) by the time it was cancelled. Fixed by making set_virtual_library() ignore disable calls entirely ($2 == 0 returns immediately) — these are shared platform-capability flags multiple unrelated libraries request independently, so one consumer being disabled must never cascade-disable a dependency another still-enabled consumer needs. Verified via the same isolated test build with --disable-lib-gnutls added (the exact flag that triggers it) and a 15-minute step timeout, so a still-broken fix would fail fast rather than risk another multi-hour hang.

CI tooling

  • Added a failure-diagnostics step that dumps the toolchain env vars and the most recently modified config.log files under src/, since the only log we printed before (src/ffmpeg/ffbuild/config.log) doesn't cover failures in the other ~30 libraries the full variant builds.
  • Cached ~/.ndk (keyed on the fixed NDK version) and prebuilt/ + src/ (keyed on a hash of android.sh + scripts/**, no restore-keys fallback — android.sh's incremental build only checks whether prebuilt/<arch>/<lib> exists, not what flags built it, so a stale cache from a different script version must not be silently reused) to avoid recompiling already-succeeded libraries on every retry. Turned out actions/cache's save step is skipped entirely when the job is cancelled or times out — exactly the failure mode above — so it had never actually saved anything across several attempts; added a second, independent checkpoint mechanism in build.yml that pushes prebuilt/ + src/ to a ci-cache branch every 2 minutes in the background, since a git push that already succeeded survives a later kill.
  • Added .github/workflows/test-harfbuzz.yml, a workflow_dispatch-only diagnostic that isolates libass's dependency chain (freetype, fribidi, fontconfig, harfbuzz) plus the native ndk-build step, sharing build.yml's exact cache key so a clean run there also primes the cache for the next full build. Used to find and verify the bitfield fix above in ~10 minutes instead of multi-hour --full round trips. Delete once no longer needed for this kind of triage.

v6.0.0-lts-android — 2026-06-20

First release of the maintained fork.

Added

  • Prebuilt ffmpeg-kit-6.0-lts-arm64-v8a.aar, compiled against NDK r26c (26.2.11394342), available from Releases.
  • Android-only native build pipeline (android.sh and supporting scripts), imported from upstream and kept buildable independent of the other platform scripts upstream shipped.
  • docs/BUILD.md — WSL2/Ubuntu build guide for producing the .aar from source.
  • CONTRIBUTING.md and license upgrade to LGPL-3.0.

Changed

  • Project scope narrowed to Android only — see README § Scope for rationale. iOS/macOS/tvOS/Linux/Flutter/React Native sources from upstream are not carried into this fork.

Known gaps (tracked, not yet shipped)

  • compileSdk/targetSdk are still 33 in android/ffmpeg-kit-android-lib/build.gradle; bumping to 35 (Android 15) is planned but not done.
  • 16 KB memory page size alignment is documented as a manual check (BUILD.md § 8) but not yet verified or enforced in CI.
  • Only arm64-v8a is published as a prebuilt release; other ABIs must be built from source.
  • Maven Central publication is in progress; the local .aar is the only distribution method for now.

See README § Compatibility for the current state of these items.