Open In App

How to Use FFmpeg in Android with Example?

Improve
Improve
Like Article
Like
Save
Share
Report

FFmpeg, short for Fast-forward MPEG, is a free and open-source multimedia framework, which is able to decode, encode, transcode, mux, demux, stream, filter and play fairly all kinds of multimedia files that have been created to date. It also supports some of the eldest formats. FFmpeg compiles and runs, across various operating systems like Linux, Mac OS X, Microsoft Windows, the BSDs, Solaris, etc. among a large range of build environments, machine architectures, and configurations. Programming languages used in FFmpeg are C and Assembly language. We can do many fun kinds of stuff using Ffmpeg like, Video Compress, Audio Compress, Trim Video, Rotate Video, Crop Video, Adding filters to videos, Reverse a Video, Creating fast and slow-motion video, Fade in fade out, Merge audio and video, Creating a video from images, Convert video from one format into another, Extract Picture from Video or Sound from Video, Gifs overlay, and many more. FFmpeg is part of the workflow of hundreds of other media-related software projects, and it’s often used behind the scenes. Also, It is an internal part of software such as VLC media player, YouTube, Plex, iTunes, Shortcut, Blender, Kodi, HandBrake, it handles video and audio playback in Google Chrome, and Linux version of Firefox. FFmpeg comprises an enormous set-up of libraries and projects for dealing with video, sound, and other multimedia files and streams.

FFmpeg Libraries 

  • libavutil is a utility library to help versatile media programming. It contains portable string functions, arbitrary number generators, extra arithmetic capacities, data structures, cryptography, and core multimedia utilities.
  • libavcodec is a library that provides encoders and decoders for video/audio codecs, subtitle streams, and several bitstream channels.
  • libavformat is a library that provides multiplexing and demultiplexing framework for video/audio codecs, subtitle streams
  • libavdevice is a library containing I/O devices for getting from and delivering to numerous multimedia I/O programming systems, including Video4Linux, ALSA, and VfW.
  • libavfilter library provides a media filtering framework that contains several filters and sinks.
  • libswscale library performs exceptionally enhanced picture scaling and pixel format transformation tasks.
  • libswresample is a library that performs highly optimized but a lossy change in audio rate, change in channel layout, for example from stereo to mono, and sample format conversion operations.

Android doesn’t have efficient and robust APIs for multimedia which could provide functionalities like FFmpeg. The only API which android has is  MediaCodec API, but it is faster than FFmpeg because it uses the device hardware for video processing.

Create a Small Video Editor App in Android Studio using FFmpeg

Prerequisites: 

Before we start, we need to set up an environment to run our FFmpeg commands. There are two options to do so:

  • By building our own library
  • By using any compiled source provided by the community. There are many libraries that can be used to perform FFmpeg operations in Android. For example,
    • WritingMinds
    • Bravobit
    • tanersener/mobile-ffmpeg
    • yangjie10930/EpMedia: There are many inbuilt functions present in this library from which you can clip, crop, rotate, add a logo, add a custom filter, merge different videos.

Although it is highly recommended to build your library, because that will reduce your apk size, you can add a third-party library and can update the library with time as you want. But, this process is very time consuming and requires extra skills. So, as a beginner, you can use some above-mentioned libraries and if you face some issue you can raise that issue on their respective GitHub repository. In the below example I will be using tanersener/mobile-ffmpeg, as it has support for Android 10 scoped storage, and also it is the best library available on the internet for FFmpeg mobile. A sample GIF is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using the Java language. 

FFmpeg in Android Sample GIF

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Note that select Java as the programming language.

Step 2: Adding a dependency to the build.gradle file

We will be using tanersener/mobile-ffmpeg library to implement FFmpeg functionalities in our app. And we will also require a rangeseekbar to select the particular length of the video. So add these dependencies in build.gradle file.

  • implementation ‘com.arthenica:mobile-ffmpeg-full:4.4’
  • implementation ‘org.florescu.android.rangeseekbar:rangeseekbar-library:0.3.0’

Step 3: Working with the colors.xml file

Below is the code for the colors.xml file.

XML




<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPost">#0091EA</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#E25E14</color>
    <color name="colorAccentTrans">#BEE25E14</color>
  
    <color name="maincolor">#E25E14</color>
  
    <color name="yellow">#feeb3c</color>
    <color name="white">#fff</color>
    <color name="semitranswhitecolor">#00FFFFFF</color>
    <color name="colorwhite_50">#CCffffff</color>
    <color name="colorwhite_10">#1Affffff</color>
    <color name="colorwhite_30">#4Dffffff</color>
    <color name="black">#2F2F2F</color>
    <color name="graycolor">#D3D3D3</color>
    <color name="graycolor2">#C5C4C4</color>
    <color name="gainsboro">#DCDCDC</color>
    <color name="lightgraycolor">#f2f2f2</color>
    <color name="darkgray">#93959A</color>
    <color name="darkgraytrans">#9493959A</color>
    <color name="darkgraytransPost">#9BC5C6C9</color>
    <color name="dimgray">#696969</color>
    <color name="lightblack">#5d5d5d</color>
  
    <color name="delete_message_bg">#f2f2f2</color>
    <color name="delete_message_text">#b8b8b8</color>
  
    <color name="transparent">#00ffffff</color>
    <color name="fifty_transparent_black">#802F2F2F</color>
  
    <color name="redcolor">#ff0008</color>
    <color name="semitransredcolor">#93FF0008</color>
    <color name="semitransredcolornew">#918E8E</color>
  
    <color name="color_gray_alpha">#65b7b7b7</color>
    <color name="app_blue">#0e1f2f</color>
    <color name="text_color">#000000</color>
    <color name="seekbar_color">#3be3e3</color>
    <color name="line_color">#FF15FF00</color>
    <color name="shadow_color">#00000000</color>
  
    <color name="app_color">#c52127</color>
  
</resources>


 
 

Step 4: Working with the activity_main.xml file

 

Go to the activity_main.xml file and refer to the following code. Below is the code for the activity_main.xml file.

 

XML




<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#43AF47"
    tools:context=".MainActivity">
  
    <RelativeLayout
        android:id="@+id/relative1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_margin="10dp">
  
        <Button
            android:id="@+id/cancel_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:background="@color/transparent"
            android:text="Select Video"
            android:textColor="@color/white" />
  
    </RelativeLayout>
  
  
    <VideoView
        android:id="@+id/layout_movie_wrapper"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/relative"
        android:layout_below="@+id/relative1" />
  
    <TextView
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:paddingBottom="5dp" />
  
    <RelativeLayout
        android:id="@+id/imagelinear"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/relative"
        android:layout_below="@+id/relative1"
        android:layout_centerInParent="true">
  
        <TextView
            android:id="@+id/overlaytextview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="Raghav"
            android:textAppearance="@style/TextAppearance.AppCompat.Medium"
            android:textColor="@color/white"
            android:textStyle="bold"
            android:visibility="gone" />
  
        <ImageView
            android:id="@+id/overlayimage"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:scaleType="fitXY" />
    </RelativeLayout>
  
    <LinearLayout
  
        android:id="@+id/relative"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical">
  
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
  
            <TextView
                android:id="@+id/textleft"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentStart="true"
                android:layout_marginBottom="10dp"
                android:text="00:00"
                android:textColor="@color/white" />
  
            <TextView
                android:id="@+id/textright"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_marginBottom="10dp"
                android:layout_weight="1"
                android:text="00:00"
                android:textAlignment="textEnd"
                android:textColor="@color/white" />
        </RelativeLayout>
  
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white">
  
            <org.florescu.android.rangeseekbar.RangeSeekBar
  
                android:id="@+id/rangeSeekBar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:activeColor="@color/white"
                app:alwaysActive="true"
                app:barHeight="2dp"
                app:showLabels="false"
                app:textAboveThumbsColor="#000000" />
  
        </RelativeLayout>
  
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="10dp" />
  
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
  
            <LinearLayout
                android:id="@+id/lineartime"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
  
                <TextView
                    android:id="@+id/text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="10dp"
                    android:text=""
                    android:textColor="@color/semitransredcolornew" />
  
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">
  
                    <LinearLayout
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_margin="10dp"
                        android:layout_weight="1"
                        android:orientation="vertical">
  
                        <ImageButton
  
                            android:id="@+id/slow"
                            android:layout_width="50dp"
                            android:layout_height="50dp"
                            android:layout_gravity="center"
                            android:background="@color/transparent"
                            android:scaleType="fitXY"
                            android:src="@drawable/icon_effect_slow" />
  
                        <TextView
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:text="Slow Motion"
                            android:textAlignment="center"
                            android:textColor="@color/white" />
                    </LinearLayout>
  
                    <LinearLayout
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_margin="10dp"
                        android:layout_weight="1"
                        android:orientation="vertical">
  
                        <ImageButton
                            android:id="@+id/reverse"
                            android:layout_width="50dp"
                            android:layout_height="50dp"
                            android:layout_gravity="center"
                            android:background="@color/transparent"
                            android:scaleType="fitXY"
                            android:src="@drawable/icon_effect_time" />
  
                        <TextView
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:text="Reverse"
                            android:textAlignment="center"
                            android:textColor="@color/white" />
                    </LinearLayout>
  
                    <LinearLayout
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_margin="10dp"
                        android:layout_weight="1"
                        android:orientation="vertical">
  
                        <ImageButton
                            android:id="@+id/fast"
                            android:layout_width="50dp"
                            android:layout_height="50dp"
                            android:layout_gravity="center"
                            android:background="@color/transparent"
                            android:scaleType="fitXY"
                            android:src="@drawable/icon_effect_repeatedly" />
  
                        <TextView
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:text="Flash"
                            android:textAlignment="center"
                            android:textColor="@color/white" />
                    </LinearLayout>
  
                </LinearLayout>
            </LinearLayout>
  
            <LinearLayout
                android:id="@+id/lineareffects"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
  
                <TextView
                    android:id="@+id/text2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="10dp"
                    android:text="Tap to add effects"
                    android:textColor="@color/white" />
            </LinearLayout>
  
        </RelativeLayout>
    </LinearLayout>
  
</RelativeLayout>


 
 

Step 5: Working with the MainActivity.java file

 

Go to the MainActivity.java file and refer to the following code. Below is the code for the MainActivity.java file. Comments are added inside the code to understand the code in more detail.

 

Java




import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.arthenica.mobileffmpeg.Config;
import com.arthenica.mobileffmpeg.ExecuteCallback;
import com.arthenica.mobileffmpeg.FFmpeg;
import org.florescu.android.rangeseekbar.RangeSeekBar;
import java.io.File;
import static com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL;
import static com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS;
  
public class MainActivity extends AppCompatActivity {
  
    private ImageButton reverse, slow, fast;
    private Button cancel;
    private TextView tvLeft, tvRight;
    private ProgressDialog progressDialog;
    private String video_url;
    private VideoView videoView;
    private Runnable r;
    private RangeSeekBar rangeSeekBar;
    private static final String root = Environment.getExternalStorageDirectory().toString();
    private static final String app_folder = root + "/GFG/";
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
  
        rangeSeekBar = (RangeSeekBar) findViewById(R.id.rangeSeekBar);
        tvLeft = (TextView) findViewById(R.id.textleft);
        tvRight = (TextView) findViewById(R.id.textright);
        slow = (ImageButton) findViewById(R.id.slow);
        reverse = (ImageButton) findViewById(R.id.reverse);
        fast = (ImageButton) findViewById(R.id.fast);
        cancel = (Button) findViewById(R.id.cancel_button);
        fast = (ImageButton) findViewById(R.id.fast);
        videoView = (VideoView) findViewById(R.id.layout_movie_wrapper);
  
        // creating the progress dialog
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setMessage("Please wait..");
        progressDialog.setCancelable(false);
        progressDialog.setCanceledOnTouchOutside(false);
  
        // set up the onClickListeners
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // create an intent to retrieve the video
                // file from the device storage
                Intent intent = new Intent(
                        Intent.ACTION_PICK,
                        android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
                        intent.setType("video/*");
                startActivityForResult(intent, 123);
            }
        });
  
        slow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // check if the user has selected any video or not
                // In case a user hasn't selected any video and press the button,
                // we will show an warning, stating "Please upload the video"
                if (video_url != null) {
                    // a try-catch block to handle all necessary exceptions
                    // like File not found, IOException
                    try {
                        slowmotion(rangeSeekBar.getSelectedMinValue().intValue() * 1000, rangeSeekBar.getSelectedMaxValue().intValue() * 1000);
                    } catch (Exception e) {
                        Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_SHORT).show();
                        e.printStackTrace();
                    }
                } else
                    Toast.makeText(MainActivity.this, "Please upload video", Toast.LENGTH_SHORT).show();
            }
        });
        fast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (video_url != null) {
  
                    try {
                        fastforward(rangeSeekBar.getSelectedMinValue().intValue() * 1000, rangeSeekBar.getSelectedMaxValue().intValue() * 1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                        Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_SHORT).show();
                    }
                } else
                    Toast.makeText(MainActivity.this, "Please upload video", Toast.LENGTH_SHORT).show();
            }
        });
        reverse.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (video_url != null) {
                    try {
                        reverse(rangeSeekBar.getSelectedMinValue().intValue() * 1000, rangeSeekBar.getSelectedMaxValue().intValue() * 1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                        Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_SHORT).show();
                    }
                } else
                    Toast.makeText(MainActivity.this, "Please upload video", Toast.LENGTH_SHORT).show();
            }
        });
  
        // set up the VideoView.
        // We will be using VideoView to view our video
        videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                // get the duration of the video
                int duration = mp.getDuration() / 1000;
  
                // initially set the left TextView to "00:00:00"
                tvLeft.setText("00:00:00");
  
                // initially set the right Text-View to the video length
                // the getTime() method returns a formatted string in hh:mm:ss
                tvRight.setText(getTime(mp.getDuration() / 1000));
  
                // this will run he video in loop
                // i.e. the video won't stop
                // when it reaches its duration
                mp.setLooping(true);
  
                // set up the initial values of rangeSeekbar
                rangeSeekBar.setRangeValues(0, duration);
                rangeSeekBar.setSelectedMinValue(0);
                rangeSeekBar.setSelectedMaxValue(duration);
                rangeSeekBar.setEnabled(true);
  
                rangeSeekBar.setOnRangeSeekBarChangeListener(new RangeSeekBar.OnRangeSeekBarChangeListener() {
                    @Override
                    public void onRangeSeekBarValuesChanged(RangeSeekBar bar, Object minValue, Object maxValue) {
                        // we seek through the video when the user
                        // drags and adjusts the seekbar
                        videoView.seekTo((int) minValue * 1000);
  
                        // changing the left and right TextView according to
                        // the minValue and maxValue
                        tvLeft.setText(getTime((int) bar.getSelectedMinValue()));
                        tvRight.setText(getTime((int) bar.getSelectedMaxValue()));
                    }
                });
  
                // this method changes the right TextView every 1 second
                // as the video is being played
                // It works same as a time counter we see in any Video Player
                final Handler handler = new Handler();
                handler.postDelayed(r = new Runnable() {
                    @Override
                    public void run() {
                        if (videoView.getCurrentPosition() >= rangeSeekBar.getSelectedMaxValue().intValue() * 1000)
                            videoView.seekTo(rangeSeekBar.getSelectedMinValue().intValue() * 1000);
                        handler.postDelayed(r, 1000);
                    }
                }, 1000);
            }
        });
    }
  
    // Method for creating fast motion video
    private void fastforward(int startMs, int endMs) throws Exception {
        // startMs is the starting time, from where we have to apply the effect.
        // endMs is the ending time, till where we have to apply effect.
        // For example, we have a video of 5min and we only want to fast forward a part of video
        // say, from 1:00 min to 2:00min, then our startMs will be 1000ms and endMs will be 2000ms.
        // create a progress dialog and show it until this method executes.
        progressDialog.show();
  
        // creating a new file in storage
        final String filePath;
        String filePrefix = "fastforward";
        String fileExtn = ".mp4";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // With introduction of scoped storage in Android Q the primitive method gives error
            // So, it is recommended to use the below method to create a video file in storage.
            ContentValues valuesvideos = new ContentValues();
            valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "Folder");
            valuesvideos.put(MediaStore.Video.Media.TITLE, filePrefix + System.currentTimeMillis());
            valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, filePrefix + System.currentTimeMillis() + fileExtn);
            valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
            valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
            valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
            Uri uri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, valuesvideos);
  
            // get the path of the video file created in the storage.
            File file = FileUtils.getFileFromUri(this, uri);
            filePath = file.getAbsolutePath();
  
        } else {
            // This else statement will work for devices with Android version lower than 10
            // Here, "app_folder" is the path to your app's root directory in device storage
            File dest = new File(new File(app_folder), filePrefix + fileExtn);
            int fileNo = 0;
            // check if the file name previously exist. Since we don't want
            // to overwrite the video files
            while (dest.exists()) {
                fileNo++;
                dest = new File(new File(app_folder), filePrefix + fileNo + fileExtn);
            }
            // Get the filePath once the file is successfully created.
            filePath = dest.getAbsolutePath();
        }
        String exe;
        // the "exe" string contains the command to process video.The details of command are discussed later in this post.
        // "video_url" is the url of video which you want to edit. You can get this url from intent by selecting any video from gallery.
        exe = "-y -i " + video_url + " -filter_complex [0:v]trim=0:" + startMs / 1000 + ",setpts=PTS-STARTPTS[v1];[0:v]trim=" + startMs / 1000 + ":" + endMs / 1000 + ",setpts=0.5*(PTS-STARTPTS)[v2];[0:v]trim=" + (endMs / 1000) + ",setpts=PTS-STARTPTS[v3];[0:a]atrim=0:" + (startMs / 1000) + ",asetpts=PTS-STARTPTS[a1];[0:a]atrim=" + (startMs / 1000) + ":" + (endMs / 1000) + ",asetpts=PTS-STARTPTS,atempo=2[a2];[0:a]atrim=" + (endMs / 1000) + ",asetpts=PTS-STARTPTS[a3];[v1][a1][v2][a2][v3][a3]concat=n=3:v=1:a=1 " + "-b:v 2097k -vcodec mpeg4 -crf 0 -preset superfast " + filePath;
  
        // Here, we have used he Async task to execute our query because
        // if we use the regular method the progress dialog
        // won't be visible. This happens because the regular method and
        // progress dialog uses the same thread to execute
        // and as a result only one is a allowed to work at a time.
        // By using we Async task we create a different thread which resolves the issue.
        long executionId = FFmpeg.executeAsync(exe, new ExecuteCallback() {
            @Override
            public void apply(final long executionId, final int returnCode) {
                if (returnCode == RETURN_CODE_SUCCESS) {
                    // after successful execution of ffmpeg command,
                    // again set up the video Uri in VideoView
                    videoView.setVideoURI(Uri.parse(filePath));
  
                    // change the video_url to filePath, so that we could
                    // do more manipulations in the
                    // resultant video. By this we can apply as many effects
                    // as we want in a single video.
                    // Actually there are multiple videos being formed in
                    // storage but while using app it
                    // feels like we are doing manipulations in only one video
                    video_url = filePath;
                    // play the result video in VideoView
                    videoView.start();
                    // remove the progress dialog
                    progressDialog.dismiss();
                } else if (returnCode == RETURN_CODE_CANCEL) {
                    Log.i(Config.TAG, "Async command execution cancelled by user.");
                } else {
                    Log.i(Config.TAG, String.format("Async command execution failed with returnCode=%d.", returnCode));
                }
            }
        });
    }
  
    // Method for creating slow motion video for specific part of the video
    // The below code is same as above only the command in string "exe" is changed
    private void slowmotion(int startMs, int endMs) throws Exception {
  
        progressDialog.show();
        final String filePath;
        String filePrefix = "slowmotion";
        String fileExtn = ".mp4";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  
            ContentValues valuesvideos = new ContentValues();
            valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "Folder");
            valuesvideos.put(MediaStore.Video.Media.TITLE, filePrefix + System.currentTimeMillis());
            valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, filePrefix + System.currentTimeMillis() + fileExtn);
            valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
            valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
            valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
            Uri uri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, valuesvideos);
            File file = FileUtils.getFileFromUri(this, uri);
            filePath = file.getAbsolutePath();
  
        } else {
            File dest = new File(new File(app_folder), filePrefix + fileExtn);
            int fileNo = 0;
            while (dest.exists()) {
                fileNo++;
                dest = new File(new File(app_folder), filePrefix + fileNo + fileExtn);
            }
            filePath = dest.getAbsolutePath();
        }
        String exe;
        exe = "-y -i " + video_url + " -filter_complex [0:v]trim=0:" + startMs / 1000 + ",setpts=PTS-STARTPTS[v1];[0:v]trim=" + startMs / 1000 + ":" + endMs / 1000 + ",setpts=2*(PTS-STARTPTS)[v2];[0:v]trim=" + (endMs / 1000) + ",setpts=PTS-STARTPTS[v3];[0:a]atrim=0:" + (startMs / 1000) + ",asetpts=PTS-STARTPTS[a1];[0:a]atrim=" + (startMs / 1000) + ":" + (endMs / 1000) + ",asetpts=PTS-STARTPTS,atempo=0.5[a2];[0:a]atrim=" + (endMs / 1000) + ",asetpts=PTS-STARTPTS[a3];[v1][a1][v2][a2][v3][a3]concat=n=3:v=1:a=1 " + "-b:v 2097k -vcodec mpeg4 -crf 0 -preset superfast " + filePath;
  
        long executionId = FFmpeg.executeAsync(exe, new ExecuteCallback() {
  
            @Override
            public void apply(final long executionId, final int returnCode) {
                if (returnCode == RETURN_CODE_SUCCESS) {
  
                    videoView.setVideoURI(Uri.parse(filePath));
                    video_url = filePath;
                    videoView.start();
                    progressDialog.dismiss();
  
                } else if (returnCode == RETURN_CODE_CANCEL) {
                    Log.i(Config.TAG, "Async command execution cancelled by user.");
                } else {
                    Log.i(Config.TAG, String.format("Async command execution failed with returnCode=%d.", returnCode));
                }
            }
        });
    }
  
    // Method for reversing the video
    // The below code is same as above only the command is changed.
    private void reverse(int startMs, int endMs) throws Exception {
        progressDialog.show();
        String filePrefix = "reverse";
        String fileExtn = ".mp4";
  
        final String filePath;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  
            ContentValues valuesvideos = new ContentValues();
            valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "Folder");
            valuesvideos.put(MediaStore.Video.Media.TITLE, filePrefix + System.currentTimeMillis());
            valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, filePrefix + System.currentTimeMillis() + fileExtn);
            valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
            valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
            valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
            Uri uri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, valuesvideos);
            File file = FileUtils.getFileFromUri(this, uri);
            filePath = file.getAbsolutePath();
  
        } else {
            filePrefix = "reverse";
            fileExtn = ".mp4";
            File dest = new File(new File(app_folder), filePrefix + fileExtn);
            int fileNo = 0;
            while (dest.exists()) {
                fileNo++;
                dest = new File(new File(app_folder), filePrefix + fileNo + fileExtn);
            }
            filePath = dest.getAbsolutePath();
        }
  
        long executionId = FFmpeg.executeAsync("-y -i " + video_url + " -filter_complex [0:v]trim=0:" + endMs / 1000 + ",setpts=PTS-STARTPTS[v1];[0:v]trim=" + startMs / 1000 + ":" + endMs / 1000 + ",reverse,setpts=PTS-STARTPTS[v2];[0:v]trim=" + (startMs / 1000) + ",setpts=PTS-STARTPTS[v3];[v1][v2][v3]concat=n=3:v=1 " + "-b:v 2097k -vcodec mpeg4 -crf 0 -preset superfast " + filePath, new ExecuteCallback() {
            @Override
            public void apply(final long executionId, final int returnCode) {
                if (returnCode == RETURN_CODE_SUCCESS) {
                    videoView.setVideoURI(Uri.parse(filePath));
                    video_url = filePath;
                    videoView.start();
                    progressDialog.dismiss();
                } else if (returnCode == RETURN_CODE_CANCEL) {
                    Log.i(Config.TAG, "Async command execution cancelled by user.");
                } else {
                    Log.i(Config.TAG, String.format("Async command execution failed with returnCode=%d.", returnCode));
                }
            }
        });
    }
  
    // Overriding the method onActivityResult() 
    // to get the video Uri form intent.
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
  
        if (resultCode == RESULT_OK) {
            if (requestCode == 123) {
                if (data != null) {
                    // get the video Uri
                    Uri uri = data.getData();
                    try {
                        // get the file from the Uri using getFileFromUri() method present
                        // in FileUils.java
                        File video_file = FileUtils.getFileFromUri(this, uri);
                          
                        // now set the video uri in the VideoView
                        videoView.setVideoURI(uri);
                          
                        // after successful retrieval of the video and properly
                        // setting up the retried video uri in
                        // VideoView, Start the VideoView to play that video
                        videoView.start();
                          
                        // get the absolute path of the video file. We will require
                        // this as an input argument in
                        // the ffmpeg command.
                        video_url = video_file.getAbsolutePath();
                    } catch (Exception e) {
                        Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
                        e.printStackTrace();
                    }
                }
            }
        }
    }
  
    // This method returns the seconds in hh:mm:ss time format
    private String getTime(int seconds) {
        int hr = seconds / 3600;
        int rem = seconds % 3600;
        int mn = rem / 60;
        int sec = rem % 60;
        return String.format("%02d", hr) + ":" + String.format("%02d", mn) + ":" + String.format("%02d", sec);
    }
}


Step 6: Creating a new Java Class FileUtils.java

Refer to How to Create Classes in Android Studio to create a new java class in Android Studio. This is a Utility file that will help in retrieving the File from a Uri. Below is the code for the FileUtils.java file. Comments are added inside the code to understand the code in more detail.

Java




import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import java.io.File;
  
public class FileUtils {
  
    // Get a file from a Uri.
    // Framework Documents, as well as the _data field for the MediaStore and
    // other file-based ContentProviders.
    // @param context The context.
    // @param uri     The Uri to query
    public static File getFileFromUri(final Context context, final Uri uri) throws Exception {
  
        String path = null;
  
        // DocumentProvider
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (DocumentsContract.isDocumentUri(context, uri)) { // TODO: 2015. 11. 17. KITKAT
  
                // ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];
  
                    if ("primary".equalsIgnoreCase(type)) {
                        path = Environment.getExternalStorageDirectory() + "/" + split[1];
                    }
  
                    // TODO handle non-primary volumes
  
                } else if (isDownloadsDocument(uri)) { // DownloadsProvider
                    final String id = DocumentsContract.getDocumentId(uri);
                    final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
                    path = getDataColumn(context, contentUri, null, null);
                } else if (isMediaDocument(uri)) { // MediaProvider
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];
  
                    Uri contentUri = null;
                    if ("image".equals(type)) {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                    } else if ("video".equals(type)) {
                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                    } else if ("audio".equals(type)) {
                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                    }
  
                    final String selection = "_id=?";
                    final String[] selectionArgs = new String[]{
                            split[1]
                    };
                    path = getDataColumn(context, contentUri, selection, selectionArgs);
                // MediaStore (and general)
            } else if ("content".equalsIgnoreCase(uri.getScheme())) {
                path = getDataColumn(context, uri, null, null);
            }
            // File
            else if ("file".equalsIgnoreCase(uri.getScheme())) {
                path = uri.getPath();
            }
            return new File(path);
        } else {
            Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
            return new File(cursor.getString(cursor.getColumnIndex("_data")));
        }
    }
  
    // Get the value of the data column for this Uri. This is useful for
    // MediaStore Uris, and other file-based ContentProviders.
    // @param context       The context.
    // @param uri           The Uri to query.
    // @param selection     (Optional) Filter used in the query.
    // @param selectionArgs (Optional) Selection arguments used in the query.
    // @return The value of the _data column, which is typically a file path.
    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        final String column = MediaStore.Images.Media.DATA;
        final String[] projection = {
                column
        };
  
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }
  
  
    // @param uri The Uri to check.
    // @return Whether the Uri authority is ExternalStorageProvide
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }
  
    // @param uri The Uri to check.
    // @return Whether the Uri authority is DownloadsProvider.
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }
  
    // @param uri The Uri to check.
    // @return Whether the Uri authority is MediaProvider.
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }
}


Output:

 

Github Project Link: https://github.com/raghavtilak/VideoEditor

Few FFmpeg commands that you can play with

  • concat video of different framerates in .mkv format:
    • -i input1.mp4 -i input2.mp4 -filter_complex [0:v:0][0:a:0][1:v:0][1:a:0]concat=n=2:v=1:a=1[outv][outa] -map [outv] -map [outa] output.mkv
  • concat video with no sound/audio:
    • -y -i input.mp4 -filter_complex [0:v]trim=0:0,setpts=PTS-STARTPTS[v1];[0:v]trim=0:5,setpts=0.5*(PTS-STARTPTS)[v2];[0:v]trim=5,setpts=PTS-STARTPTS[v3];[v1][v2][v3]concat=n=3:v=1:a=0 -b:v 2097k -vcodec mpeg4 -crf 0 -preset superfast output.mp4
  • Textoverlay:
    • -y -i input.mp4 -vf drawtext=”fontsize=30:fontfile=cute.ttf:text=’GFG'”:x=w-tw-10:y=h-th-10 -c:v libx264 -preset ultrafast outputmp4
  • gif/png/jpeg overlay
    • -i input.mp4 -i inputimage.png -filter_complex [1:v]scale=320:394[ovr1],[0:v][ovr1]overlay=0:0:enable=’between(t,0,16)’ -c:a copy output.mp4, (or)
    • -i input.mp4 -i inputimage.png -filter_complex overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2:enable=’between(t,0,7)’ -c:a copy output.mp4
  • Add subtitles to a video file
    • -i input.mp4 -i subtitle.srt -map 0 -map 1 -c copy -c:v libx264 -crf 23 -preset superfast output.mp4
  • Converting video files to audio files
    • -i input.mp4 -vn output.mp3
  • Cropping videos
    • -i input.mp4 -filter:v “crop=w:h:x:y” output.mp4
      • w – width of the rectangle which we are intended to crop from the source video.
      • h – the height of that rectangle.
      • x – the x coordinate of that rectangle.
      • y – the y coordinate of the rectangle.
  • Adding a poster image to audio files
    • -loop 1 -i inputimage.jpg -i inputaudio.mp3 -c:v libx264 -c:a aac -strict experimental -b:a 192k -shortest output.mp4

Pros of using FFmpeg

  1. It is also highly portable.
  2. It is profoundly valuable for the transcoding of all kinds of multimedia files into a single common format.
  3. You don’t need heavy Third-party VideoEditors like Adobe Premiere Pro, Filmora for small editing tasks.

Cons of using FFmpeg

  1. It’s difficult for beginners to use and implement.
  2. It takes some time to process. We don’t get results in a second or two.
  3. The official documentation is quite confusing and it’s not beginner-friendly.
  4. APK size becomes very large. The FFmpeg libraries alone will use 30-70MB depending upon the libraries you are including.

Alternatives of FFmpeg

  • MediaCodec Android
  • LiTr
  • Gstreamer
  • MP4Parser
  • Intel INDE Media for Mobile

Notes:

1. If you set -preset to a higher value say, ultrafast then the video processing will be fast but the quality of the video will be compromised. The lower the -preset higher the quality of the video.

2. You can change the -crf value to change the quality of the output video. Lower the crf value higher the quality of the video.

3. If you use -y in starting of command then this means that if a file is present with the same name as that of the output file name that FFmpeg will overwrite the existing file.

4. In the case of video, to slow down the video set -PTS value larger than 1. The larger the value slower the video, Lower the value Faster the video. But in the case of Audio this is just the opposite, i.e. Larger the value faster the Audio, the Lower the value slower the audio.

5. The atempo(audio) filter is limited to using values between 0.5 and 2.0 (so it can slow it down to no less than half the original speed, and speed up to no more than double the input)

6. FFmpeg takes too much time working with audio. If the video file doesn’t contain the audio we need not to command FFmpeg to work with audio, and hence this will reduce the workload and we will get the processed video fast/in less time.



Last Updated : 23 Nov, 2022
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads