JMR-devandClaude Opus 5 ec6e37ad08 Stop selecting FFmpeg's MediaCodec encoders, and pin the pixel format
Testing on a Pixel 10 Pro XL with real footage found two defects that neither
the emulator nor any unit test could surface.

The sample is H.264 High 4:4:4 Predictive (avc1.F4001F). Media3 cannot decode
it on any device: hardware AVC decoders implement High 4:2:0, and Android's
software c2.google.avc.decoder does not cover 4:4:4 either, so the export dies
with a codec exception. The static routing rules cannot predict this -- the
container is MP4, the codec is "h264", and the device reports AVC decode and
encode -- so it is exactly the case the runtime fallback exists for. Until a
file like this was tried on hardware, that fallback had never actually fired.

Following it through exposed the two bugs behind it.

First, only one video encode path named a pixel format. FFmpeg decodes 4:4:4
to yuv444p and hands those frames to encoders that cannot accept them; naming
yuv420p makes it insert the conversion instead. Every path now does.

Second, and not fixed by that: hevc_mediacodec still failed with "Error
submitting video frame to the encoder". That is the second distinct failure
from FFmpeg's MediaCodec wrappers in one session -- the first being that on a
device with no hardware encoder they silently bind to the platform software
codec and crawl while presenting as the fast path. They are undocumented,
per-device flaky, and duplicate badly something Media3 already does properly.
A job only reaches FFmpeg because Media3 could not handle it, which is itself
evidence hardware encode is unlikely to work for that input.

So FFmpeg now always encodes in software, and Fast means a fast preset rather
than a different encoder: libx264/libx265 at veryfast against medium, both
with CRF. With that, the fallback completes and the file converts.

Measured on the Pixel: AV1 1080p through the hardware path runs at 7.9x
realtime, confirming the figure the two-engine design was based on; software
x264 CRF at 720p runs at 4.4x. Hardware encoders present are av01, avc and
hevc -- AV1 encode included, which is still rare.

29 instrumented tests pass on device, 63 unit tests on the JVM.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 09:39:43 -05:00
2026-08-19 17:29:15 -05:00
2026-08-19 17:29:15 -05:00
2026-08-19 17:29:15 -05:00
2026-08-19 17:29:15 -05:00

Media Converter

A free and open-source media converter for Android — batch video transcoding and compression, audio extraction and conversion, GIF and frame export, and file merging.

Android 13+ (API 33). Built with Jetpack Compose and Material 3.

Status: working, unreleased. Both conversion engines, the router, the background job queue and the join flow are implemented and building. The FFmpeg format tests have been written but not yet executed on a device.

Licensing at a glance

  • Source code: MIT
  • Distributed APK: GPL-3.0 — because it bundles FFmpeg built with x264/x265

That split is deliberate, not an oversight. See LICENSES/README.md for the reasoning and the corresponding-source obligations.

Architecture

Two conversion engines behind an explicit router, because neither one covers the job alone.

AndroidX Media3 Transformer — the hardware path

Handles the common cases: MP4/MOV in and out, H.264/HEVC, resolution and frame-rate changes, rotation, overlays, audio to AAC, and stream-copy transmuxing. Fully hardware accelerated end to end — MediaCodec decodes to a GL surface and MediaCodec re-encodes, so frames never round-trip through the CPU. Roughly 7–8× realtime on 720p.

FFmpeg — the long tail

Everything Media3 structurally cannot do:

  • Containers outside MP4/WebM/Ogg/WAV/AAC — MKV, AVI, FLV, MPEG-TS
  • MP3 output — Android has no MP3 encoder at any version; this is a platform gap
  • GIF and image sequences
  • Input codecs with no platform decoder on the device
  • CRF and 2-pass rate control, for the quality tier

Quality tiers

The router is surfaced to users as a quality choice rather than hidden:

Tier Engine Rate control Trade-off
Fast (default) Media3 / MediaCodec bitrate-targeted ~7–8× realtime, low battery cost
Best quality FFmpeg + x264/x265 CRF or 2-pass ~realtime or slower, better quality per byte

A note on "GPU acceleration"

Android has no GPU video codec path. There are three distinct tiers, and conflating them causes a lot of confusion:

  1. Fixed-function video codec silicon — reached through MediaCodec. This is what "hardware accelerated" means for encode and decode. It is not the GPU.
  2. GPU shader cores — genuinely used, but only for filters, scaling, and color effects on already-decoded frames, via OpenGL ES. Never for entropy coding.
  3. CPU — x264, x265, and software decoders.

FFmpeg's -hwaccel is meaningful on Android only as mediacodec, and even then it targets direct-to-Surface playback rather than file-to-file transcoding. Vulkan Video exists in FFmpeg 8.0+ but no shipping Android GPU driver exposes it — no VK_KHR_video_* extension appears in any Android Vulkan Profile tier.

So this app is hardware accelerated via MediaCodec, and GPU accelerated for effects via GL shaders. Both are real; neither is "the GPU decoding video."

Features

Formats
Video out MP4 (H.264/H.265), MKV (H.264/H.265), WebM (VP9)
Audio out MP3, AAC/M4A, FLAC, Opus, WAV
Images GIF, PNG frame sequences
Other Join several files into one

Conversions run as durable background work, so they survive leaving the app and are restored after a restart.

Building

Requires JDK 17+ (AGP 9 will not run on older) and the Android SDK with API 37.

The FFmpeg AAR is not committed — it is a 35 MB binary, and F-Droid strips checked-in native libraries. Build it first:

cd tools/ffmpeg
podman build -t ffmpeg-kit-builder:local -f Containerfile .
mkdir -p out
podman run --name ffmpeg-build -v "$PWD/out":/work/out:Z \
    localhost/ffmpeg-kit-builder:local full
cp out/ffmpeg-kit-next-*.aar ../../app/libs/

Then:

./gradlew :app:assembleDebug          # debug APK
./gradlew :app:testDebugUnitTest      # JVM tests
./gradlew :app:connectedDebugAndroidTest   # device tests, needs a running device
./gradlew :app:assembleRelease        # R8-minified release

See tools/ffmpeg/README.md for why the build is containerised and which flags matter.

Testing

Unit tests cover the parts that decide correctness without needing hardware: the routing matrix, the FFmpeg argument builder, and the stream-copy-versus-re-encode planner. They run against fabricated device profiles, so branches like "this device cannot encode HEVC" are reachable regardless of what the test machine is.

Instrumented tests cover the parts that only a device can prove: real hardware transcoding, the foreground service type, and each FFmpeg output format asserted against the produced file rather than the exit code.

Privacy

The app has no INTERNET permission, so it cannot open a network connection at all. Nothing is uploaded, and there is no analytics or advertising. See PRIVACY.md, which also explains the permissions WorkManager adds automatically.

Contributing

Contributions are welcome. Note that contributions to the source are under MIT, while the distributed binary remains GPL-3.0 for the reasons described in LICENSES/README.md.

S
Description
On-device FOSS media converter for mobile. Android first, iOS aspirational. Android flavor uses Media3 and FFMPEG.
Readme MIT
70 MiB
Languages
Kotlin 91.1%
Shell 7.4%
Java 1.4%
Dockerfile 0.1%