├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── animated_vector_drawable_23_2_1.xml │ ├── appcompat_v7_23_2_1.xml │ ├── butterknife_5_1_1.xml │ ├── hamcrest_core_1_3.xml │ ├── junit_4_12.xml │ ├── support_annotations_23_2_1.xml │ ├── support_v4_23_2_1.xml │ └── support_vector_drawable_23_2_1.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml ├── vcs.xml └── workspace.xml ├── README.md ├── build.gradle ├── ffmpeg_java_wrapper_demo ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── shutup │ │ └── ffmpegcmdutils │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── shutup │ │ │ └── ffmpegcmdutils │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── shutup │ └── ffmpegcmdutils │ └── ExampleUnitTest.java ├── ffmpeg_java_wrapper_lib ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── net │ │ └── sourceforge │ │ │ └── sox │ │ │ ├── CrossfadeCat.java │ │ │ └── SoxController.java │ └── org │ │ └── ffmpeg │ │ └── android │ │ ├── Clip.java │ │ ├── FFmpegController.java │ │ ├── ShellUtils.java │ │ ├── filters │ │ ├── CropVideoFilter.java │ │ ├── DrawBoxVideoFilter.java │ │ ├── DrawTextVideoFilter.java │ │ ├── FadeVideoFilter.java │ │ ├── OverlayVideoFilter.java │ │ ├── RedactVideoFilter.java │ │ ├── TransposeVideoFilter.java │ │ └── VideoFilter.java │ │ └── test │ │ ├── ConcatTest.java │ │ ├── ConvertTest.java │ │ ├── CrossfadeTest.java │ │ ├── FilterTest.java │ │ ├── MixTest.java │ │ └── Tests.java │ └── res │ ├── drawable-hdpi │ ├── ic_action_search.png │ └── ic_launcher.png │ ├── drawable-mdpi │ ├── ic_action_search.png │ └── ic_launcher.png │ ├── drawable-xhdpi │ ├── ic_action_search.png │ └── ic_launcher.png │ ├── raw │ ├── ffmpeg │ └── sox │ └── values │ └── strings.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | ### Android template 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | 39 | # Keystore files 40 | *.jks 41 | 42 | # Created by .ignore support plugin (hsz.mobi) 43 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | android-ffmpeg-java -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | -------------------------------------------------------------------------------- /.idea/libraries/animated_vector_drawable_23_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/appcompat_v7_23_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/butterknife_5_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_annotations_23_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_23_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/support_vector_drawable_23_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Android 39 | 40 | 41 | Android Lint 42 | 43 | 44 | Java language level migration aids 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 1.7 71 | 72 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # android-ffmpeg-java-demo 2 | #说明 3 | 以前一直希望可以做一个播放器,所以当时就接触了ffmpeg这个库,当时打算使用JNI的方式进行底层的调用,无奈整个逻辑比较麻烦,因此进度一再搁浅。 4 | 5 | 后来进一步的了解中发现,其实对于视频的处理,方法是很多的 6 | * 直接以C的代码进行处理,调用ffmpeg库的函数 7 | * JAVA在命令行调用C的程序进行处理,调用ffmpeg程序 8 | 9 | 经过一段时间的探索,对于ffmpeg的交叉编译已经没什么大问题了,那么我们就来使用这个库吧。 10 | 11 | 这个demo主要演示,通过JAVA在命令行调用FFMPEG的二进制程序来完成一些视频的处理功能。这个ffmpeg的二进制程序是在交叉编译的过程中生成的。使用的java wrapper是[guardianproject's android-ffmpeg-java](https://github.com/guardianproject/android-ffmpeg-java),当然我自己有做一些优化,比如使用我自己编译的最新的ffmpeg替换了它原版使用的ffmpeg程序,开发环境也换到了android studio,还添加了一些方法的实现。 12 | 13 | ##视频剪切 14 | 原理:JAVA开启一个命令行,在命令行中调用ffmpeg的程序,根据传入的参数进行相关处理。 15 | 16 | ``` 17 | ffmpeg -ss 00:00:00 -t 00:00:30 -i test.mp4 -vcodec copy -acodec copy output.mp4 18 | * -ss 指定从什么时间开始 19 | * -t 指定需要截取多长时间 20 | * -i 指定输入文件 21 | ``` 22 | ##视频合并 23 | 原理:JAVA开启一个命令行,在命令行中调用ffmpeg的程序,根据传入的参数进行相关处理。 24 | 25 | ``` 26 | //进行视频的合并 27 | ffmpeg -f concat -i list.txt -c copy concat.mp4 28 | ``` 29 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.0.0' 8 | } 9 | } 10 | 11 | allprojects { 12 | repositories { 13 | jcenter() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ffmpeg_java_wrapper_demo/.gitignore: -------------------------------------------------------------------------------- 1 | ### Android template 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | 39 | # Keystore files 40 | *.jks 41 | 42 | -------------------------------------------------------------------------------- /ffmpeg_java_wrapper_demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.shutup.ffmpegcmdutils" 9 | minSdkVersion 10 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.2.1' 26 | compile project(':ffmpeg_java_wrapper_lib') 27 | compile 'com.jakewharton:butterknife:5.1.1' 28 | } 29 | -------------------------------------------------------------------------------- /ffmpeg_java_wrapper_demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/shutup/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /ffmpeg_java_wrapper_demo/src/androidTest/java/com/shutup/ffmpegcmdutils/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.shutup.ffmpegcmdutils; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /ffmpeg_java_wrapper_demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ffmpeg_java_wrapper_demo/src/main/java/com/shutup/ffmpegcmdutils/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.shutup.ffmpegcmdutils; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.os.Environment; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import org.ffmpeg.android.Clip; 16 | import org.ffmpeg.android.FFmpegController; 17 | import org.ffmpeg.android.ShellUtils; 18 | 19 | import java.io.File; 20 | import java.util.ArrayList; 21 | 22 | import butterknife.ButterKnife; 23 | import butterknife.InjectView; 24 | import butterknife.OnClick; 25 | 26 | public class MainActivity extends AppCompatActivity { 27 | 28 | private static final String TAG = "FFMpeg Cmd Utils"; 29 | private final String IMAGE_TYPE = "image/*"; 30 | private final String VIDEO_TYPE = "video/*"; 31 | 32 | private final int RESULT_CODE = 0; //这里的RESULT_CODE是自己任意定义的 33 | 34 | private final int SELECT_FIRST = 1; 35 | private final int SELECT_SECOND = 2; 36 | 37 | @InjectView(R.id.BtnClip) 38 | Button mBtnClip; 39 | @InjectView(R.id.BtnConcat) 40 | Button mBtnConcat; 41 | @InjectView(R.id.BtnSelect) 42 | Button mBtnSelect; 43 | @InjectView(R.id.info) 44 | TextView mInfo; 45 | @InjectView(R.id.editTextStartTime) 46 | EditText mEditTextStartTime; 47 | @InjectView(R.id.editTextDuration) 48 | EditText mEditTextDuration; 49 | @InjectView(R.id.editTextSplitName) 50 | EditText mEditTextSplitName; 51 | @InjectView(R.id.BtnFirstItem) 52 | Button mBtnFirstItem; 53 | @InjectView(R.id.BtnSecondItem) 54 | Button mBtnSecondItem; 55 | @InjectView(R.id.editTextConcatName) 56 | EditText mEditTextConcatName; 57 | @InjectView(R.id.BtnClean) 58 | Button mBtnClean; 59 | private String srcVideoPath = null; 60 | private String mFirstVideoPath = null; 61 | private String mSecondVideoPath = null; 62 | 63 | @Override 64 | protected void onCreate(Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | setContentView(R.layout.activity_main); 67 | ButterKnife.inject(this); 68 | 69 | } 70 | 71 | @OnClick({R.id.BtnSelect, R.id.BtnClip, R.id.BtnFirstItem, R.id.BtnSecondItem, R.id.BtnConcat}) 72 | public void onClick(View view) { 73 | switch (view.getId()) { 74 | case R.id.BtnSelect: 75 | jumpToSelectVideo(RESULT_CODE); 76 | break; 77 | case R.id.BtnClip: 78 | clipTheVideoTest(); 79 | break; 80 | case R.id.BtnFirstItem: 81 | jumpToSelectVideo(SELECT_FIRST); 82 | break; 83 | case R.id.BtnSecondItem: 84 | jumpToSelectVideo(SELECT_SECOND); 85 | break; 86 | case R.id.BtnConcat: 87 | concatTheVideoTest(); 88 | break; 89 | } 90 | } 91 | 92 | @Override 93 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 94 | super.onActivityResult(requestCode, resultCode, data); 95 | if (resultCode != RESULT_OK) { //此处的 RESULT_OK 是系统自定义得一个常量 96 | Log.e(TAG, "ActivityResult resultCode error"); 97 | return; 98 | 99 | } 100 | //此处的用于判断接收的Activity是不是你想要的那个 101 | if (requestCode == RESULT_CODE) { 102 | Uri originalUri = data.getData(); //获得所选内容的uri 103 | srcVideoPath = originalUri.getPath(); 104 | mBtnSelect.setText(srcVideoPath); 105 | Log.d(TAG, "onActivityResult: " + srcVideoPath); 106 | } else if (requestCode == SELECT_FIRST) { 107 | Uri originalUri = data.getData(); //获得所选内容的uri 108 | mFirstVideoPath = originalUri.getPath(); 109 | mBtnFirstItem.setText(mFirstVideoPath); 110 | Log.d(TAG, "onActivityResult: " + srcVideoPath); 111 | } else if (requestCode == SELECT_SECOND) { 112 | Uri originalUri = data.getData(); //获得所选内容的uri 113 | mSecondVideoPath = originalUri.getPath(); 114 | mBtnSecondItem.setText(mSecondVideoPath); 115 | Log.d(TAG, "onActivityResult: " + srcVideoPath); 116 | } 117 | } 118 | 119 | @OnClick(R.id.BtnClean) 120 | public void onClick() { 121 | String dstDirName = "ffmpegTest"; 122 | String sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 123 | String dstDir = sdcardPath + File.separator + dstDirName; 124 | File dstDirFile = new File(dstDir); 125 | if (dstDirFile.exists()) { 126 | File[] files = dstDirFile.listFiles(); 127 | for (File fileVideoOutput : files) { 128 | fileVideoOutput.delete(); 129 | } 130 | } else { 131 | dstDirFile.mkdir(); 132 | } 133 | } 134 | 135 | private void jumpToSelectVideo(int resultCode) { 136 | //使用intent调用系统提供的相册功能,使用startActivityForResult是为了获取用户选择的内容 137 | Intent getAlbum = new Intent(Intent.ACTION_GET_CONTENT); 138 | // getAlbum.setType(IMAGE_TYPE); 139 | getAlbum.setType(VIDEO_TYPE); 140 | 141 | startActivityForResult(getAlbum, resultCode); 142 | } 143 | 144 | private void clipTheVideoTest() { 145 | if (srcVideoPath == null) { 146 | Toast.makeText(MainActivity.this, "select the video first", Toast.LENGTH_SHORT).show(); 147 | return; 148 | } 149 | String dstDirName = "ffmpegTest"; 150 | String sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 151 | String dstDir = sdcardPath + File.separator + dstDirName; 152 | File dstDirFile = new File(dstDir); 153 | if (dstDirFile.exists()) { 154 | 155 | } else { 156 | dstDirFile.mkdir(); 157 | } 158 | 159 | 160 | try { 161 | FFmpegController fc = new FFmpegController(MainActivity.this, new File(dstDir)); 162 | Clip in = new Clip(); 163 | in.startTime = mEditTextStartTime.getText().toString().trim(); 164 | in.path = srcVideoPath; 165 | 166 | final Clip out = new Clip(); 167 | out.duration = Double.parseDouble(mEditTextDuration.getText().toString().trim()); 168 | out.audioCodec = "copy"; 169 | out.videoCodec = "copy"; 170 | out.path = dstDir + File.separator + mEditTextSplitName.getText().toString().trim(); 171 | 172 | fc.clipVideo(in, out, true, new ShellUtils.ShellCallback() { 173 | @Override 174 | public void shellOut(String shellLine) { 175 | Log.d(TAG, "shellOut() returned: " + shellLine); 176 | } 177 | 178 | @Override 179 | public void processComplete(int exitValue) { 180 | Log.d(TAG, "processComplete() returned: " + exitValue); 181 | Toast.makeText(MainActivity.this, "the new clip is at:" + out.path, Toast.LENGTH_LONG).show(); 182 | } 183 | }); 184 | } catch (Exception e) { 185 | e.printStackTrace(); 186 | } 187 | 188 | } 189 | 190 | private void concatTheVideoTest() { 191 | if (mFirstVideoPath == null || mSecondVideoPath == null) { 192 | Toast.makeText(MainActivity.this, "select the video first", Toast.LENGTH_SHORT).show(); 193 | return; 194 | } 195 | String dstDirName = "ffmpegTest"; 196 | String sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath(); 197 | String dstDir = sdcardPath + File.separator + dstDirName; 198 | File dstDirFile = new File(dstDir); 199 | if (dstDirFile.exists()) { 200 | 201 | } else { 202 | dstDirFile.mkdir(); 203 | } 204 | 205 | 206 | try { 207 | FFmpegController fc = new FFmpegController(MainActivity.this, new File(dstDir)); 208 | Clip in = new Clip(); 209 | in.path = mFirstVideoPath; 210 | Clip in1 = new Clip(); 211 | in1.path = mSecondVideoPath; 212 | ArrayList videos = new ArrayList<>(); 213 | videos.add(in); 214 | videos.add(in1); 215 | 216 | 217 | final Clip out = new Clip(); 218 | out.path = dstDir + File.separator + mEditTextConcatName.getText().toString().trim(); 219 | 220 | fc.concatVideo(videos, out, true, new ShellUtils.ShellCallback() { 221 | @Override 222 | public void shellOut(String shellLine) { 223 | Log.d(TAG, "shellOut() returned: " + shellLine); 224 | } 225 | 226 | @Override 227 | public void processComplete(int exitValue) { 228 | Log.d(TAG, "processComplete() returned: " + exitValue); 229 | Toast.makeText(MainActivity.this, "the new concat video is at:" + out.path, Toast.LENGTH_LONG).show(); 230 | } 231 | }); 232 | } catch (Exception e) { 233 | e.printStackTrace(); 234 | } 235 | } 236 | 237 | 238 | } 239 | -------------------------------------------------------------------------------- /ffmpeg_java_wrapper_demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 16 | 20 | 24 |