FFmpeg是Fast-forward MPEG的缩写,是一个免费的开源多媒体框架,它能够解码,编码,转码,mux,demux,流,过滤和播放迄今已创建的各种多媒体文件。 。它还支持某些最老的格式。 FFmpeg可在各种构建环境,机器体系结构和配置中的各种操作系统(如Linux,Mac OS X,Microsoft Windows,BSD,Solaris等)上编译和运行。 FFmpeg中使用的编程语言是C和汇编语言。我们可以使用Ffmpeg来做很多有趣的事情,例如,视频压缩,音频压缩,修剪视频,旋转视频,裁剪视频,向视频添加滤镜,反转视频,创建慢动作和慢动作视频,淡入淡出,合并音频和视频,从图像创建视频,将视频从一种格式转换为另一种格式,从视频中提取图片或从视频中提取声音,Gif叠加图等等。 FFmpeg是数百个其他与媒体相关的软件项目的工作流的一部分,并且经常在幕后使用。而且,它是VLC媒体播放器,YouTube,Plex,iTunes,Shotcut,Blender,Kodi,HandBrake等软件的内部组件,它可以处理Google Chrome和Linux版本的Firefox中的视频和音频播放。 FFmpeg包含用于处理视频,声音和其他多媒体文件和流的庞大的库和项目设置。
FFmpeg库
- libavutil是一个实用程序库,可帮助进行多种媒体编程。它包含可移植的字符串函数,任意数字生成器,额外的算术功能,数据结构,密码学和核心多媒体实用程序。
- libavcodec是一个库,为视频/音频编解码器,字幕流和几个比特流通道提供编码器和解码器。
- libavformat是一个库,为视频/音频编解码器,字幕流提供复用和解复用框架
- libavdevice是一个包含I / O设备的库,该I / O设备用于从Video4Linux,ALSA和VfW等众多多媒体I / O编程系统中获取和交付。
- libavfilter库提供了一个包含多个过滤器和接收器的媒体过滤框架。
- libswscale库执行了特别增强的图片缩放和像素格式转换任务。
- libswresample是一个执行高度优化的库,但音频率发生了有损变化,通道布局发生了变化,例如从立体声转换为单声道,并且采样格式转换操作。
Android没有有效且强大的多媒体API,这些API可能提供FFmpeg之类的功能。 android唯一的API是MediaCodec API,但它比FFmpeg更快,因为它使用设备硬件进行视频处理。
使用FFmpeg在Android Studio中创建小型视频编辑器应用
先决条件:
在开始之前,我们需要设置一个环境来运行FFmpeg命令。有两种选择:
- 通过建立我们自己的图书馆
- 通过使用社区提供的任何编译源。有许多库可用于在Android中执行FFmpeg操作。例如,
- 写作心智
- 布拉沃比特
- 制革机/移动ffmpeg
- yangjie10930 / EpMedia:此库中存在许多内置函数,您可以从中剪切,裁剪,旋转,添加徽标,添加自定义滤镜,合并不同的视频。
尽管强烈建议您构建库,因为这样做会减小apk的大小,但是您可以添加第三方库,并可以根据需要随时间更新库。但是,此过程非常耗时,并且需要额外的技能。因此,作为初学者,您可以使用一些上述库,如果遇到问题,可以在各自的GitHub存储库上提出该问题。在下面的示例中,我将使用tanersener / mobile-ffmpeg,因为它支持Android 10范围存储,并且它也是FFmpeg mobile在互联网上可用的最佳库。下面给出了一个示例GIF,以了解我们将在本文中做些什么。注意,我们将使用Java语言实现该项目。
分步实施
步骤1:创建一个新项目
要在Android Studio中创建新项目,请参阅如何在Android Studio中创建/启动新项目。请注意,选择Java作为编程语言。
步骤2:将依赖项添加到build.gradle文件
我们将使用tanersener / mobile-ffmpeg库在我们的应用程序中实现FFmpeg功能。而且,我们还将需要一个rangeeekbar来选择视频的特定长度。因此,将这些依赖项添加到build.gradle文件中。
- implementation ‘com.arthenica:mobile-ffmpeg-full:4.4’
- implementation ‘org.florescu.android.rangeseekbar:rangeseekbar-library:0.3.0’
步骤3:使用colors.xml文件
以下是colors.xml文件的代码。
XML
#3F51B5
#0091EA
#303F9F
#E25E14
#BEE25E14
#E25E14
#feeb3c
#fff
#00FFFFFF
#CCffffff
#1Affffff
#4Dffffff
#2F2F2F
#D3D3D3
#C5C4C4
#DCDCDC
#f2f2f2
#93959A
#9493959A
#9BC5C6C9
#696969
#5d5d5d
#f2f2f2
#b8b8b8
#00ffffff
#802F2F2F
#ff0008
#93FF0008
#918E8E
#65b7b7b7
#0e1f2f
#000000
#3be3e3
#FF15FF00
#00000000
#c52127
XML
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() methid 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);
}
}
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());
}
}
步骤4:使用activity_main.xml文件
转到activity_main.xml文件,并参考以下代码。以下是activity_main.xml文件的代码。
XML格式
步骤5:使用MainActivity。 Java文件
转到MainActivity。 Java文件并参考以下代码。下面是MainActivity的代码。 Java文件。在代码内部添加了注释,以更详细地了解代码。
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() methid 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);
}
}
步骤6:创建一个新的Java类FileUtils。Java
请参阅如何在Android Studio中创建类以在Android Studio中创建新的Java类。这是一个实用程序文件,有助于从Uri检索文件。下面是FileUtils的代码。 Java文件。在代码内部添加了注释,以更详细地了解代码。
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());
}
}
输出:
Github项目链接: https : //github.com/raghavtilak/VideoEditor
很少可以使用的FFmpeg命令
- concate 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
- concate 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.
- -i input.mp4 -filter:v “crop=w:h:x:y” output.mp4
- 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
使用FFmpeg的优点
- 它也是高度便携式的。
- 对于将各种多媒体文件转换为单一通用格式而言,它具有极其重要的价值。
- 您不需要繁琐的第三方VideoEditor(如Adobe Premiere Pro,Filmora)即可完成小的编辑任务。
使用FFmpeg的缺点
- 对于初学者来说,很难使用和实现。
- 处理需要一些时间。我们在一两秒钟之内没有得到结果。
- 官方文档非常混乱,而且对初学者不友好。
- APK大小变得非常大。 FFmpeg库将单独使用30-70MB,具体取决于所包含的库。
FFmpeg的替代品
- MediaCodec Android
- 锂离子
- Gstreamer
- MP4解析器
- 英特尔INDE移动媒体
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.