Rebuild the FFmpeg AAR with libvorbis, and make Ogg Vorbis reachable (#254) #261

Merged
JMR-dev merged 3 commits from feat/ogg-vorbis-libvorbis into main 2026-09-07 18:25:38 +00:00
JMR-dev commented 2026-09-06 23:42:46 +00:00 (Migrated from github.com)

Closes #254.

FFmpegCommandBuilder emitted -c:a libvorbis, and libvorbis was not in the AAR this app
ships
. The arm was not merely unreached — it could not have succeeded; the first user to pick Ogg
Vorbis would have got Unknown encoder 'libvorbis'. This rebuilds the bundled FFmpeg with
libvorbis and implements Ogg Vorbis on it properly: -c:a libvorbis -q:a 5, no -strict experimental, no forced -ac 2, mono preserved.

The configure flag, and how it was confirmed

--enable-libvorbis — not the --enable-vorbis the ticket predicted. ffmpeg-kit's
--enable-* names come from its own get_library_name() (--enable-lame for libmp3lame,
--enable-opus for libopus), so the name was read rather than extrapolated, out of the
checked-out scripts/function.sh in the build container before any compile started:

  62:  9) echo "libvorbis" ;;                    # get_library_name
 248:  libvorbis) echo 9 ;;
 977:  echo -e "  --enable-libvorbis\t\tbuild with libvorbis [no]"
1283:  libvorbis)
1284:    ENABLED_LIBRARIES[LIBRARY_LIBVORBIS]=$2
1285:    ENABLED_LIBRARIES[LIBRARY_LIBOGG]=$2     # libogg comes with it, unasked

It went into COMMON_LIBS beside --enable-lame and --enable-opus, for the same reason those
are there: Android has no Vorbis encoder at any API level either.

Evidence that libvorbis is really in the binary

Checked against the artifact before bin/README.md was touched, because ffmpeg-kit ignores an
unrecognised --enable-* silently and a dropped library looks exactly like a successful build.

== libvorbis present?  (before -> after)
  jni/arm64-v8a/libavcodec.so : MISSING -> libvorbis
  jni/x86_64/libavcodec.so    : MISSING -> libvorbis

== external lib* encoder names, x86_64 libavcodec.so
  libdav1d libjxl libjxl_anim libmp3lame libopus libopusdec libsvtav1
  libvorbis libvpx libx264 libx264rgb libx265        <- libvorbis is the only new one

== configure line out of libavutil.so (both ABIs)
  ... --enable-libsvtav1 --enable-libvorbis --enable-libvpx ...   <- one flag added, nothing moved
  FFmpeg version n8.1.2                                          <- unchanged

== shape
  20 shared libraries, 10 per ABI    (unchanged)
  77 LOAD segments, all 0x4000       (16 KB page alignment intact)

sha256  ae188c9aec3c89a1c87a169589253c85438d57cfdcc3ce8b40fb3e87de368ff2  (old)
sha256  c8f4491d2c626566cbf18d5035513c1a5d8049e6696531342ea030c5427df507  (new)

bin/README.md carries the new checksum, the new configure line, a Rebuilt row, and the two ways
this flag can be got wrong. tools/ffmpeg/README.md's "easy to get wrong" list gained a third
entry for the same reason.

How the build was run — disclosed, because it was incremental

podman commit of ffmpeg-full, the container that produced the currently shipped AAR, then a
full build in it with the new flag. The starting point is provably the shipped binary: that
container's AAR is ae188c9a…, byte for byte what bin/ held. Only libogg and libvorbis
built fresh; everything else reported already built, and ffmpeg was reconfigured and relinked for
both ABIs. 14 minutes, not the 40 the ticket budgeted. The configure-line delta above is the check
that this produced the intended binary rather than a stale one.

The mutations, run rather than predicted (API 34 emulator)

The ticket's acceptance criterion — point AudioCodec.VORBIS at libopus:

FFmpegEngineTest.encodesOggVorbisThroughAnEncoderTheBundledBinaryActuallyHas FAILED
java.lang.AssertionError: expected a lone Vorbis track -- an Opus one would carry the same
OggS magic expected:<[audio/vorbis]> but was:<[audio/opus]>

Note which assertion caught it: the OggS magic assertion passed, because the mutant writes a
perfectly good Ogg. A magic-only test would have been vacuous here, which is why the track MIME is
asserted.

And the mono one — add -ac 2 back, the compromise the in-tree encoder would force:

java.lang.AssertionError: the fixture is mono and libvorbis takes any channel count, so
nothing may upmix it expected:<[1]> but was:<[2]>

Restored after each; the whole FFmpegEngineTest class (12 tests) is green against the new AAR.

Mono

Confirmed, and pinned rather than asserted in prose. sample_h264.mp4 — the class's own fixture —
is mono (ffprobe … channels=1), so the passing test's channelCounts(out) == [1] is a real
check, as the second mutation above shows. That was the user-visible compromise the in-tree encoder
forced, and it is gone.

Did the emulator sweep run?

Yes. pre-commit printed app/src changed -- sweeping API 33, 34, 35, 36 (this takes tens of minutes, by design) and ran all four against the rebuilt AAR:

API 33: tests=72 failures=0 errors=0 skipped=3
API 34: tests=72 failures=0 errors=0 skipped=3
API 35: tests=72 failures=0 errors=0 skipped=3
API 36: tests=72 failures=0 errors=0 skipped=3
[local-gate] NOT COVERED LOCALLY: API 37. No API 37 device is attached ...
[local-gate] green on API 33, 34, 35, 36; pre-commit allowed

pre-push then read the cache — app/src (8a9b316e...) already swept and green — as designed. The
Starting 72 tests line is also the cross-check CLAUDE.md asks for on the derived count.

Worth knowing regardless: the gate classifies by path, matching app/src/main/* and
app/src/{test,androidTest}/* and nothing else. bin/ is invisible to it, so an AAR rebuilt on
its own — a different native binary under every instrumented test — would invalidate no cache and
sweep nothing, while the JVM gate that does run cannot execute FFmpeg at all. This commit is not
affected (the AAR and the app/src change ship together), and CLAUDE.md now records it.

Three things that contradicted the ticket

  • The flag is --enable-libvorbis, not the --enable-vorbis the ticket expected the
    get_library_name() rule to produce. The rule is real; libvorbis is simply one of the names that
    agrees with FFmpeg's.
  • The rebuild took 14 minutes, not 40, because the container that built the shipped AAR still
    had every other library built.
  • There is no separate mono fixture. sample_h264.mp4, which FFmpegEngineTest already uses for
    everything, is mono — so mono needed one more assertion in the existing test, not a new test.

What is not covered

API 37 was not swept locally (no Pixel attached; the API 37 emulator cannot install the APK on this
host). The new test carries no @FailsOnEmulatorApi37, so CI's gating leg runs it — the premise
holds there: the job encodes entirely inside FFmpeg and then asks the platform's software
OggExtractor, with none of the c2.goldfish codecs that break the Media3 tests involved. The
baseline stays at 7; the derived counts move to 72 instrumented tests, 65 on the gating leg, and
CLAUDE.md is updated with both.

Closes #254. `FFmpegCommandBuilder` emitted `-c:a libvorbis`, and **libvorbis was not in the AAR this app ships**. The arm was not merely unreached — it could not have succeeded; the first user to pick Ogg Vorbis would have got `Unknown encoder 'libvorbis'`. This rebuilds the bundled FFmpeg with libvorbis and implements Ogg Vorbis on it properly: `-c:a libvorbis -q:a 5`, no `-strict experimental`, no forced `-ac 2`, mono preserved. ## The configure flag, and how it was confirmed **`--enable-libvorbis`** — *not* the `--enable-vorbis` the ticket predicted. ffmpeg-kit's `--enable-*` names come from its own `get_library_name()` (`--enable-lame` for libmp3lame, `--enable-opus` for libopus), so the name was read rather than extrapolated, out of the checked-out `scripts/function.sh` in the build container before any compile started: ``` 62: 9) echo "libvorbis" ;; # get_library_name 248: libvorbis) echo 9 ;; 977: echo -e " --enable-libvorbis\t\tbuild with libvorbis [no]" 1283: libvorbis) 1284: ENABLED_LIBRARIES[LIBRARY_LIBVORBIS]=$2 1285: ENABLED_LIBRARIES[LIBRARY_LIBOGG]=$2 # libogg comes with it, unasked ``` It went into `COMMON_LIBS` beside `--enable-lame` and `--enable-opus`, for the same reason those are there: Android has no Vorbis encoder at any API level either. ## Evidence that libvorbis is really in the binary Checked against the artifact **before** `bin/README.md` was touched, because ffmpeg-kit ignores an unrecognised `--enable-*` silently and a dropped library looks exactly like a successful build. ``` == libvorbis present? (before -> after) jni/arm64-v8a/libavcodec.so : MISSING -> libvorbis jni/x86_64/libavcodec.so : MISSING -> libvorbis == external lib* encoder names, x86_64 libavcodec.so libdav1d libjxl libjxl_anim libmp3lame libopus libopusdec libsvtav1 libvorbis libvpx libx264 libx264rgb libx265 <- libvorbis is the only new one == configure line out of libavutil.so (both ABIs) ... --enable-libsvtav1 --enable-libvorbis --enable-libvpx ... <- one flag added, nothing moved FFmpeg version n8.1.2 <- unchanged == shape 20 shared libraries, 10 per ABI (unchanged) 77 LOAD segments, all 0x4000 (16 KB page alignment intact) sha256 ae188c9aec3c89a1c87a169589253c85438d57cfdcc3ce8b40fb3e87de368ff2 (old) sha256 c8f4491d2c626566cbf18d5035513c1a5d8049e6696531342ea030c5427df507 (new) ``` `bin/README.md` carries the new checksum, the new configure line, a `Rebuilt` row, and the two ways this flag can be got wrong. `tools/ffmpeg/README.md`'s "easy to get wrong" list gained a third entry for the same reason. ### How the build was run — disclosed, because it was incremental `podman commit` of `ffmpeg-full`, the container that produced the currently shipped AAR, then a `full` build in it with the new flag. The starting point is provably the shipped binary: that container's AAR is `ae188c9a…`, byte for byte what `bin/` held. Only `libogg` and `libvorbis` built fresh; everything else reported `already built`, and ffmpeg was reconfigured and relinked for both ABIs. 14 minutes, not the 40 the ticket budgeted. The configure-line delta above is the check that this produced the intended binary rather than a stale one. ## The mutations, run rather than predicted (API 34 emulator) **The ticket's acceptance criterion** — point `AudioCodec.VORBIS` at `libopus`: ``` FFmpegEngineTest.encodesOggVorbisThroughAnEncoderTheBundledBinaryActuallyHas FAILED java.lang.AssertionError: expected a lone Vorbis track -- an Opus one would carry the same OggS magic expected:<[audio/vorbis]> but was:<[audio/opus]> ``` Note *which* assertion caught it: the `OggS` magic assertion passed, because the mutant writes a perfectly good Ogg. A magic-only test would have been vacuous here, which is why the track MIME is asserted. **And the mono one** — add `-ac 2` back, the compromise the in-tree encoder would force: ``` java.lang.AssertionError: the fixture is mono and libvorbis takes any channel count, so nothing may upmix it expected:<[1]> but was:<[2]> ``` Restored after each; the whole `FFmpegEngineTest` class (12 tests) is green against the new AAR. ## Mono Confirmed, and pinned rather than asserted in prose. `sample_h264.mp4` — the class's own fixture — is **mono** (`ffprobe … channels=1`), so the passing test's `channelCounts(out) == [1]` is a real check, as the second mutation above shows. That was the user-visible compromise the in-tree encoder forced, and it is gone. ## Did the emulator sweep run? **Yes.** `pre-commit` printed `app/src changed -- sweeping API 33, 34, 35, 36 (this takes tens of minutes, by design)` and ran all four against the rebuilt AAR: ``` API 33: tests=72 failures=0 errors=0 skipped=3 API 34: tests=72 failures=0 errors=0 skipped=3 API 35: tests=72 failures=0 errors=0 skipped=3 API 36: tests=72 failures=0 errors=0 skipped=3 [local-gate] NOT COVERED LOCALLY: API 37. No API 37 device is attached ... [local-gate] green on API 33, 34, 35, 36; pre-commit allowed ``` `pre-push` then read the cache — `app/src (8a9b316e...) already swept and green` — as designed. The `Starting 72 tests` line is also the cross-check `CLAUDE.md` asks for on the derived count. **Worth knowing regardless:** the gate classifies by path, matching `app/src/main/*` and `app/src/{test,androidTest}/*` and nothing else. **`bin/` is invisible to it**, so an AAR rebuilt on its own — a different native binary under every instrumented test — would invalidate no cache and sweep nothing, while the JVM gate that does run cannot execute FFmpeg at all. This commit is not affected (the AAR and the `app/src` change ship together), and `CLAUDE.md` now records it. ## Three things that contradicted the ticket - The flag is `--enable-libvorbis`, not the `--enable-vorbis` the ticket expected the `get_library_name()` rule to produce. The rule is real; libvorbis is simply one of the names that agrees with FFmpeg's. - The rebuild took **14 minutes**, not 40, because the container that built the shipped AAR still had every other library built. - There is no separate mono fixture. `sample_h264.mp4`, which `FFmpegEngineTest` already uses for everything, is mono — so mono needed one more assertion in the existing test, not a new test. ## What is not covered API 37 was not swept locally (no Pixel attached; the API 37 emulator cannot install the APK on this host). The new test carries no `@FailsOnEmulatorApi37`, so CI's gating leg runs it — the premise holds there: the job encodes entirely inside FFmpeg and then asks the platform's software `OggExtractor`, with none of the `c2.goldfish` codecs that break the Media3 tests involved. The baseline stays at 7; the derived counts move to **72 instrumented tests, 65 on the gating leg**, and `CLAUDE.md` is updated with both.
Sign in to join this conversation.