FFMPEG is a CLI tool that can be used out-of-the-box on desktop operating systems such as macOS, Linux, and Windows. However, on a mobile OS, such as Android, we have to use a mobile FFMPEG wrapper to abstract low-level implementations and complexities that come with interacting with the FFMPEG core library (written in C).
In this article, our goal is to convert a video file into a GIF using FFMPEG on Android. We will learn how to request media content from Android Storage Providers in the process. To effectively follow through, you will need a basic understanding of Kotlin and Android development and be ready to learn about FFMPEG. Let’s get started.
Jump ahead:
FFMPEG is a suite of CLI tools for manipulating media files. It has a rich set of libraries for performing basic to advance operations on media files. One common use case of FFMPEG is converting video files from one format to another, such as files from MOV to AVI video containers. Technically, this process is referred to as transcoding and may occur for a variety of reasons. Some of the reasons include the following:
FFMPEG’s capabilities extend far beyond simple video conversion. It also offers a range of options for trimming videos, such as selecting specific time ranges, cutting out sections, or splitting videos into multiple parts. In addition to these features, FFMPEG can also be used for other tasks, such as extracting audio from videos, creating GIFs, adding watermarks, custom video filters, and more. Its flexibility and wide range of capabilities have made it an essential tool for developers and video editors.
FFmpeg Kit is a toolkit or wrapper to use FFMPEG in applications. It supports multiple mobile development environments like Android, iOS, Flutter, and React Native. The beauty of this toolkit is its unified API. You don’t have to learn a new syntax when switching between different environments. The API names are designed to be consistent across all platforms.
Create a new Android Studio project and add the FFmpeg Kit library as a dependency into your app-level build.gradle
file:
implementation 'com.arthenica:ffmpeg-kit-full:5.1'
In this step, we will access the user’s shared storage to select any video file. As per Android’s permission rules, the user grants permission to the request at runtime. To request this permission, specify this in the Android AndroidManifest.xml
file like so:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Remember that the code block above alone does not satisfy the permission guidelines for storage. In addition to the permission defined in the manifest
file, we’ll make a request at runtime. See the code block below:
val permission = Manifest.permission.WRITE_EXTERNAL_STORAGE val permissionCheck = ContextCompat.checkSelfPermission(this, permission) if (permissionCheck != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(permission), PERMISSION_CODE) } else { // Your code to select file from the external storage }
In the code above, we first check if Manifest.permission.WRITE_EXTERNAL_STORAGE
has been granted by the user. We request permission to access the external storage if that has not been granted. If the if expression is false, we will go ahead and launch the documents browser.
The traditional approach for starting an activity for results has been removed in favor of the AndroidX Activity Result API. The improved API is type-safe and has good abstraction, making it easy to test. Below is a classic example of how to use this API:
val contract = ActivityResultContracts.GetContent() val callback = ActivityResultCallback<Uri?> { uri -> // do something here } val launcher = registerForActivityResult(contract, callback) launcher.launch("video/*")
In only four simple and easy-to-read lines of code, we implemented "launch activity for result"
.
In line one, we define the kind of intent with GetContent()
contract. The expected behavior of this contract is for the user to pick a piece of content and return the result to the activity that launched the intent. ActivityResultCallback
is triggered whenever the result from the user selection is available.
The registerForActivityResult()
is used to register the request to launch an activity for a result. You can register multiple contracts and callbacks. The last piece of this code is launching the intent by calling launch
with "video/*"
as an argument for the mime type.
FFMPEG commands have varying degrees of complexity depending on the level of control you want over the result. It spans from basic to advanced settings. In most cases, the basic commands can serve well because FFMPEG cleverly chooses its default settings. Usually, it automatically selects the correct codecs
and container
without any complex configurations.
The basic command to convert a video to a GIF is this:
ffmpeg -i input.mp4 output.gif //this is a command line FFMPEG command
For now, the most important parts are the input
and output
paths. The input
path is extracted from the content URI returned within the callback
we defined earlier. The input
path will be the absolute path
of the user’s selection. Similarly, for the output
path, we need to specify where the file path
will come from. See the code block below:
val num = Random.nextInt(4600) val outputFile = File(getExternalFilesDir(null), "output$num.gif") val outPutFilePath = outputFile.absolutePath
We create a file in the external storage directory that is specific to our application, and then we’ll get the absolute path
of the same file. Your command should look like this:
val ffmpegCommand = "-i $inputFilePath $outPutFilePath"
As explained earlier, we are not working with the CLI FFPEG. We are using a mobile wrapper to execute FFMPEG commands. The code below executes the FFMPEG:
FFmpegKit.executeAsync(ffmpegCommand) { session -> if (ReturnCode.isSuccess(session.returnCode)) { // SUCCESS } else { // FAILURE } }
The code above is non-blocking and does not block the UI thread. Then, in the lambda callback
, we check the status of the operation. Viola! We’ve successfully converted a video file into a GIF. Congrats on making it this far 👏. To see the output result, check your application’s root directory. In my case, it is:
Android/data/user/com.enyason.ffmpegkitdemo/files/output{num}.gif
output
GIF qualityIn the command we used to convert a video to a GIF file, we relied on the default FFMPEG settings. Usually, this is fine. However, if we want fine-grain control over the output, we have to specify the settings ourselves. See the updated command below:
val ffmpegCommand = "-i $inputFilePath -t 10 -r 24 $outPutFilePath"
-t 10
tells FFMPEG to trim the output
to only 10
seconds while -r 24
is the frame rate for the output GIF. Higher frame rates produce smoother GIFs. However, higher framerates significantly increase a GIF’s file size. Another setting that is not captured in the command above is frame size. By default, it takes the dimension of the original video. To scale the video, use -s 480x320
.
output
GIFWhat are GIFs sitting on our gallery for? Let’s share them with friends 😊. The code block below shares the output
file with supported applications:
private fun shareGif(file: File) { val gifUri = FileProvider.getUriForFile(this, "com.example.fileprovider", file) val shareIntent = Intent(Intent.ACTION_SEND).apply { type = "image/gif" putExtra(Intent.EXTRA_STREAM, gifUri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } startActivity(shareIntent) }
One thing to note is that the code block above is how we’ve constructed the content URI for the file that would be shared. Using the FileProvider
returns the content URI
(instead of file URI
) that is managed by the Android system. This way, Android protects user privacy and security so that external apps cannot access the file system through File URI
s.
This was the last piece of this article. If you have successfully followed through, well done! To view the complete project, check out the GitHub repo here.
The capabilities of FFMPEG are numerous and robust. It has become the backbone for many applications like VLC media player, HandBrake, Audacity, WhatsApp, and more. The list goes on, but the examples are proof of how successful the tool has been.
FFMPEG provides a library suite that serves many use cases, especially for building video editing software. You may want to explore this technology if you are excited about multi-media for mobile. Cheers!
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.