A powerful, standalone FFmpeg library for Android that works on all Android versions including Android 10+ without root access. This library provides a complete FFmpeg 6.0 implementation with extensive codec support, hardware acceleration, and a simple Kotlin API.
β
Android 10+ Support - Works on Android 10, 11, 12, 13, 14, 15+ using JNI wrapper approach
β
No External Dependencies - Self-contained library with built-in FFmpeg binaries
β
Extensive Codec Support - H.264, H.265, VP8/VP9, MPEG4, AAC, MP3, LAME, Opus, and more
β
Hardware Acceleration - MediaCodec support for faster encoding/decoding
β
300+ Filters - All major video and audio filters included
β
Network Protocols - HTTP, HTTPS, RTMP, HLS streaming support
β
Kotlin Coroutines - Modern async API with suspend functions
β
Progress Tracking - Real-time progress callbacks for long operations
β
Session Management - Cancel and manage multiple FFmpeg operations
β
Automatic Library Download - FFmpeg libraries are downloaded automatically from GitHub Release
- Add JitPack repository to your root
build.gradle.ktsorsettings.gradle.kts:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}- Add dependency to your app's
build.gradle.kts:
dependencies {
implementation("com.github.mzgs:FFmpegX-Android:v2.2.1")
}- Clone the repository and add the library module to your project in
settings.gradle.kts:
include(":ffmpegx")
project(":ffmpegx").projectDir = File("path/to/FFmpegX-Android/ffmpegx")- Add dependency to your app's
build.gradle.kts:
dependencies {
implementation(project(":ffmpegx"))
}class MainActivity : ComponentActivity() {
private lateinit var ffmpeg: FFmpeg
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize FFmpeg
ffmpeg = FFmpeg.initialize(this)
// FFmpeg auto-installs on first use, but you can do it manually
lifecycleScope.launch {
if (!ffmpeg.isInstalled()) {
ffmpeg.install()
}
}
}
}- Java 17 - Required by Android Gradle Plugin 8.12+
- Android SDK - API level 34
- Gradle 8.13+ - Included via wrapper
Note: JitPack builds handle Java 17 automatically. For local builds, install Java 17:
# macOS with Homebrew
brew install openjdk@17
# Or download from Adoptium
# https://adoptium.net/temurin/releases/?version=17lifecycleScope.launch {
// Execute any FFmpeg command
val success = ffmpeg.execute("-i input.mp4 -c:v mpeg4 output.mp4")
// With progress tracking
ffmpeg.execute(
command = "-i input.mp4 -vf scale=720:480 output.mp4",
callback = object : FFmpegHelper.FFmpegCallback {
override fun onStart() {
Log.d("FFmpeg", "Started")
}
override fun onProgress(progress: Float, time: Long) {
Log.d("FFmpeg", "Progress: ${(progress * 100).toInt()}%")
}
override fun onSuccess(output: String?) {
Log.d("FFmpeg", "Success!")
}
override fun onFailure(error: String) {
Log.e("FFmpeg", "Failed: $error")
}
override fun onFinish() {
Log.d("FFmpeg", "Finished")
}
override fun onOutput(line: String) {
// Process output line by line
}
}
)
}lifecycleScope.launch {
ffmpeg.operations().compressVideo(
inputPath = "/storage/emulated/0/DCIM/video.mp4",
outputPath = "/storage/emulated/0/DCIM/compressed.mp4",
quality = FFmpegOperations.VideoQuality.MEDIUM, // LOW, MEDIUM, HIGH, VERY_HIGH
callback = object : FFmpegHelper.FFmpegCallback {
override fun onProgress(progress: Float, time: Long) {
runOnUiThread {
progressBar.progress = (progress * 100).toInt()
}
}
// ... other callbacks
}
)
}lifecycleScope.launch {
val success = ffmpeg.operations().extractAudio(
inputPath = videoFile.path,
outputPath = "${videoFile.parent}/audio.mp3",
audioFormat = FFmpegOperations.AudioFormat.MP3 // MP3, AAC, WAV, FLAC, OGG
)
}// Trim video (cut a segment)
lifecycleScope.launch {
ffmpeg.operations().trimVideo(
inputPath = "/path/to/input.mp4",
outputPath = "/path/to/trimmed.mp4",
startTimeSeconds = 10.0, // Start at 10 seconds
durationSeconds = 30.0 // Cut 30 seconds
)
}
// Resize video
lifecycleScope.launch {
ffmpeg.operations().resizeVideo(
inputPath = "/path/to/input.mp4",
outputPath = "/path/to/resized.mp4",
width = 1280,
height = 720,
maintainAspectRatio = true
)
}
// Rotate video
lifecycleScope.launch {
ffmpeg.operations().rotateVideo(
inputPath = "/path/to/input.mp4",
outputPath = "/path/to/rotated.mp4",
degrees = 90 // 90, 180, or 270
)
}
// Change video speed
lifecycleScope.launch {
ffmpeg.operations().changeVideoSpeed(
inputPath = "/path/to/input.mp4",
outputPath = "/path/to/fast.mp4",
speed = 2.0f, // 2x speed
adjustAudio = true
)
}
// Reverse video
lifecycleScope.launch {
ffmpeg.operations().reverseVideo(
inputPath = "/path/to/input.mp4",
outputPath = "/path/to/reversed.mp4",
reverseAudio = true
)
}// Merge multiple videos
lifecycleScope.launch {
ffmpeg.operations().mergeVideos(
videoPaths = listOf(
"/path/to/video1.mp4",
"/path/to/video2.mp4",
"/path/to/video3.mp4"
),
outputPath = "/path/to/merged.mp4"
)
}
// Add watermark to video
lifecycleScope.launch {
ffmpeg.operations().addWatermark(
videoPath = "/path/to/video.mp4",
watermarkPath = "/path/to/logo.png",
outputPath = "/path/to/watermarked.mp4",
position = FFmpegOperations.WatermarkPosition.TOP_RIGHT
// Options: TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER
)
}
// Create GIF from video
lifecycleScope.launch {
ffmpeg.operations().createGif(
inputPath = "/path/to/video.mp4",
outputPath = "/path/to/output.gif",
width = 320,
fps = 10,
startTime = 0.0,
duration = 5.0
)
}
// Extract frames as images
lifecycleScope.launch {
ffmpeg.operations().extractFrames(
videoPath = "/path/to/video.mp4",
outputPattern = "/path/to/frames/frame_%04d.jpg",
fps = 1 // Extract 1 frame per second
)
}
// Add subtitles
lifecycleScope.launch {
ffmpeg.operations().addSubtitles(
videoPath = "/path/to/video.mp4",
subtitlePath = "/path/to/subtitles.srt",
outputPath = "/path/to/subtitled.mp4"
)
}lifecycleScope.launch {
val mediaInfo = ffmpeg.getMediaInfo("/path/to/media.mp4")
mediaInfo?.let { info ->
// General information
Log.d("Media", "Duration: ${info.duration} ms")
Log.d("Media", "Bitrate: ${info.bitrate} bps")
// Video stream information
info.videoStreams.forEach { video ->
Log.d("Media", "Video Codec: ${video.codec}")
Log.d("Media", "Resolution: ${video.width}x${video.height}")
Log.d("Media", "Frame Rate: ${video.frameRate} fps")
}
// Audio stream information
info.audioStreams.forEach { audio ->
Log.d("Media", "Audio Codec: ${audio.codec}")
Log.d("Media", "Sample Rate: ${audio.sampleRate} Hz")
Log.d("Media", "Channels: ${audio.channels}")
}
}
}
// Quick checks
val isVideo = ffmpeg.operations().isVideoFile("/path/to/file.mp4")
val isAudio = ffmpeg.operations().isAudioFile("/path/to/file.mp3")
val duration = ffmpeg.operations().getVideoDuration("/path/to/video.mp4")
val resolution = ffmpeg.operations().getVideoResolution("/path/to/video.mp4")
val codec = ffmpeg.operations().getVideoCodec("/path/to/video.mp4")// Build complex commands with fluent API
val command = ffmpeg.commandBuilder()
.input("/path/to/input.mp4")
.overwriteOutput()
.videoCodec("mpeg4")
.videoBitrate("2M")
.videoFilter("scale=1280:720")
.audioCodec("aac")
.audioBitrate("128k")
.audioSampleRate(44100)
.startTime(10.0)
.duration(30.0)
.customOption("-preset", "fast")
.output("/path/to/output.mp4")
.build()
lifecycleScope.launch {
ffmpeg.execute(command)
}// Execute async with session ID
val sessionId = ffmpeg.executeAsync(
command = "-i input.mp4 -c:v mpeg4 output.mp4",
callback = myCallback
)
// Cancel specific session
ffmpeg.cancel(sessionId)
// Cancel all running sessions
ffmpeg.cancelAll()
// Get session information
val sessionManager = ffmpeg.sessions()
val activeCount = sessionManager.getActiveSessionCount()
val allSessions = sessionManager.getAllSessions()// Download and convert stream
lifecycleScope.launch {
ffmpeg.execute(
"-i https://example.com/stream.m3u8 -c copy output.mp4"
)
}
// Stream to RTMP
lifecycleScope.launch {
ffmpeg.execute(
"-re -i input.mp4 -c:v libx264 -f flv rtmp://server/live/stream"
)
}βββββββββββββββββββββββ
β Your Android App β
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β FFmpeg.kt β β Main API entry point
βββββββββββββββββββββββ€
β β’ FFmpegHelper β β Command execution & callbacks
β β’ FFmpegOperations β β Pre-built video operations
β β’ FFmpegInstaller β β Binary extraction & management
β β’ CommandBuilder β β Fluent API for commands
β β’ SessionManager β β Multi-session handling
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β JNI Layer β
βββββββββββββββββββββββ€
β β’ FFmpegNative β β Native FFmpeg integration
β β’ FFmpegTranscoder β β Hardware transcoding
β β’ MediaCodec β β Hardware acceleration
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β Native Layer β
βββββββββββββββββββββββ€
β β’ Static FFmpeg 6.0 β β Linked FFmpeg libraries
β β’ LAME MP3 Encoder β β High-quality MP3
β β’ MediaCodec H/W β β Hardware codecs
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β FFmpeg Libraries β β Auto-downloaded from
β β’ libavcodec β GitHub Release
β β’ libavformat β (103MB compressed)
β β’ libavfilter β Cached locally
βββββββββββββββββββββββ
Add to your AndroidManifest.xml:
<!-- Network operations (for streaming) -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Storage access -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<!-- Android 13+ media permissions -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- For HTTP downloads (if needed) -->
<application
android:usesCleartextTraffic="true"
...>| Type | Codecs |
|---|---|
| Decoders | H.264, H.265/HEVC, VP8, VP9, MPEG4, MJPEG, PNG, GIF, WebP |
| Encoders | libx264, MPEG4, MJPEG, PNG, GIF |
| Hardware | MediaCodec for H.264, H.265, VP8, VP9 (encode/decode) |
| Type | Codecs |
|---|---|
| Decoders | AAC, MP3, Opus, Vorbis, FLAC, WAV, WMA, AC3, DTS |
| Encoders | AAC, libmp3lame (MP3), libfdk_aac, Opus, Vorbis, FLAC, WAV |
| Input | Output |
|---|---|
| MP4, MOV, AVI, MKV, WebM, FLV, MPEGTS, HLS, M3U8, RTMP | MP4, MOV, AVI, MKV, WebM, FLV, MPEGTS, HLS, GIF |
- Video: scale, crop, rotate, overlay, fade, pad, transpose, fps, drawtext, subtitles
- Audio: volume, aresample, atempo, equalizer, compressor, normalize
- File, HTTP, HTTPS, RTMP, RTSP, HLS, FTP
- Use Hardware Decoders: Append
_mediacodecto decoder names for hardware acceleration - Copy Codecs: Use
-c copywhen you don't need re-encoding - Optimal Presets: Use MPEG4 encoder with appropriate bitrates
- Background Processing: Use
executeAsync()for long operations - Cancel Operations: Always provide cancel options for long tasks
| Issue | Solution |
|---|---|
| "Unknown encoder" error | Use mpeg4 instead of h264/libx264 |
| Slow encoding | Use hardware decoders with _mediacodec suffix |
| Large output files | Adjust bitrate and quality settings |
| Permission denied | Library handles Android 10+ automatically via JNI |
| Out of memory | Process large files in segments |
The library adds approximately 40MB to your APK (both architectures). To reduce size:
- Use App Bundle: Google Play will deliver only required architecture
- Split APKs by ABI:
android {
splits {
abi {
enable true
reset()
include "arm64-v8a", "armeabi-v7a"
}
}
}- Download on first use: Implement dynamic binary download
- Remove unused architectures: Keep only arm64-v8a for modern devices
The library automatically downloads pre-built FFmpeg libraries from GitHub Release on first build. To build custom FFmpeg:
# Clone the repository
git clone https://github.com/mzgs/FFmpegX-Android.git
cd FFmpegX-Android
# Edit build configuration
nano build-ffmpeg.sh
# Run build (requires NDK)
./build-ffmpeg.sh
# Create release package
./create-library-release.sh
# Libraries location
ffmpegx/src/main/cpp/ffmpeg-libs/
ffmpegx/src/main/cpp/lame-libs/- FFmpeg 6.0 with GPL license
- LAME MP3 encoder (high quality)
- x264 H.264 encoder
- FDK-AAC encoder
- OpenSSL for HTTPS support
- MediaCodec hardware acceleration
- All standard codecs and filters
The repository includes a full example app demonstrating:
- β Video selection from gallery/downloads
- β Video download from URL
- β Media information extraction
- β Video compression with progress
- β Audio extraction
- β Custom command execution
- β Real-time progress UI
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
This library uses FFmpeg compiled with GPL license (includes x264, LAME, FDK-AAC).
- You must comply with GPL license when distributing your app
- See FFmpeg License for details
- For commercial use, consider building FFmpeg without GPL components
- The library wrapper code is provided as-is for educational purposes
Built with β€οΈ using:
- FFmpeg 6.0 (GPL build with x264, LAME, FDK-AAC)
- Android NDK r27
- Kotlin Coroutines
- JNI for Android 10+ compatibility
- Automatic library download from GitHub Release
Note: This library is designed for legitimate use cases. Ensure you have necessary rights for any media you process.