Android(java方法)上实现mp4的分割和拼接 (二)
这节谈一下如何在android上实现mp4文件的高效率切割。
业务需求举例:把一段2分钟的mp4文件切割出00:42 至 01:16这段时间的视频,要求足够短的执行时间和尽量少的误差。
分析:mp4Parser只能在关键帧切割,比如,在00:40和00:45分别存在一个可切割关键帧,那么切割视频的头和尾,都应该选择短切割。然后获取到误差的视频短,如果这个误差大于0.5S,用FFmpeg进行一帧一帧编解码切割文件。这样最多会有三段mp4文件,再次将这三段mp4拼接起来就可以了。
下面直接上关键代码,这些代码在PC上新建一个java工程也可以实现。
1.切割文件方法:
/**
需要使用isoviewer-1.0-RC-27包
返回值是目标mp4的开头和结尾时刻
**/
- public static double[] startTrim(File src, File dst, int startMs, int endMs) throws IOException {
- Movie movie = MovieCreator.build(src.getAbsolutePath());
- List<Track> tracks = movie.getTracks();
- movie.setTracks(new LinkedList<Track>());
- double startTime = startMs/1000;
- double endTime = endMs/1000;
- boolean timeCorrected = false;
- // Here we try to find a track that has sync samples. Since we can only start decoding
- // at such a sample we SHOULD make sure that the start of the new fragment is exactly
- // such a frame
- for (Track track : tracks) {
- if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
- if (timeCorrected) {
- throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported.");
- }
- //true,false表示短截取;false,true表示长截取
- startTime = correctTimeToSyncSample(track, startTime, true);
- endTime = correctTimeToSyncSample(track, endTime, false);
- timeCorrected = true;
- }
- }
- int x = 0;
- for (Track track : tracks) {
- long currentSample = 0;
- double currentTime = 0;
- long startSample = -1;
- long endSample = -1;
- x++;
- for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
- TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
- for (int j = 0; j < entry.getCount(); j++) {
- // entry.getDelta() is the amount of time the current sample covers.
- if (currentTime <= startTime) {
- // current sample is still before the new starttime
- startSample = currentSample;
- }
- if (currentTime <= endTime) {
- // current sample is after the new start time and still before the new endtime
- endSample = currentSample;
- } else {
- // current sample is after the end of the cropped video
- break;
- }
- currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
- currentSample++;
- }
- }
- movie.addTrack(new CroppedTrack(track, startSample, endSample));
- break;
- }
- Container container = new DefaultMp4Builder().build(movie);
- if (!dst.exists()) {
- dst.createNewFile();
- }
- FileOutputStream fos = new FileOutputStream(dst);
- FileChannel fc = fos.getChannel();
- container.writeContainer(fc);
- fc.close();
- fos.close();
- double[] doubleArray = new double[2] ;
- doubleArray[0] = startTime;
- doubleArray[1] = endTime;
- return doubleArray;
- }
2.ffmpeg切割方法,需要jni实现。稍后补充
- public String getMp4ByFFmpeg(double mTimeStart,double mTimeEnd,String videoPath){
- try{
- String mFinalVideoPath = videoPath;
- int audioChannels = 2;
- FFmpegRecorder recorder = new FFmpegRecorder(
- mFinalVideoPath, RecorderConfig.TARGET_VIDEO_WIDTH,
- RecorderConfig.TARGET_VIDEO_HEIGHT, audioChannels);
- RecorderConfig.setRecorderConfig(recorder, RecorderConfig.CONFIG_TYPE_MPEG4_HIGH);
- int totalFrames = 0;
- FFmpegGrabber grabber = FFmpegGrabber.createDefault(mPath);
- grabber.setSquareSize(RecorderConfig.TARGET_VIDEO_WIDTH);
- int degree = VideoFileUtil.getRotate(mPath);
- grabber.setOrientation(degree);
- grabber.start();
- if (mTimeStart > 0) {
- grabber.setTimestamp((long)mTimeStart);
- }
- totalFrames = grabber.getLengthInFrames();
- VideoClip mFinalClip = new VideoClip();
- mFinalClip.mIsFromLocal = true;
- mFinalClip.mHeight = RecorderConfig.TARGET_VIDEO_HEIGHT;
- mFinalClip.mWidth = RecorderConfig.TARGET_VIDEO_WIDTH;
- recorder.setAudioChannels(grabber.getAudioChannels());
- recorder.setSampleRate(grabber.getSampleRate());
- recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
- recorder.setFrameRate(FFmpegRecorder.DEFAULT_FRAME_RATE);
- recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4);
- recorder.start();
- mFinalClip.mOrientation = 0;
- mFinalClip.mFrameRate = (int) recorder.getFrameRate();
- mFinalClip.mSampleRate = recorder.getSampleRate();
- mFinalClip.mAudioBitrate = recorder.getAudioBitrate();
- mFinalClip.mAudioChannels = recorder.getAudioChannels();
- Frame grabbedFrame = new Frame();
- int j = 0;
- boolean videoTimeout = false;
- boolean audioTimeout = false;
- while (grabber.grabFrame(grabbedFrame)) {
- long i = grabber.getTimestamp();
- long k = grabber.getFrameNumber();
- if (videoTimeout && audioTimeout) {
- break;
- }
- if (grabbedFrame.hasVideoFrame()) {
- int progress = 100 * (int) (i - mTimeStart) / mTotalTimeSpan;
- publishProgress(progress);
- }
- if (i > mTimeEnd) {
- if (grabbedFrame.hasAudioFrame()) {
- audioTimeout = true;
- }
- if (grabbedFrame.hasVideoFrame()) {
- videoTimeout = true;
- }
- continue;
- }
- grabbedFrame.setTimeStamp((long)(i - mTimeStart));
- recorder.recordFrameNoException(grabbedFrame);
- SLog.v(TAG, "record image at {}, #{}", i, k);
- j++;
- }
- grabbedFrame.releaseNativeAllocation();
- grabber.stop();
- grabber.release();
- recorder.stop();
- recorder.release();
- mFinalClip.mClipPath = mFinalVideoPath;
- mFinalClip.mDuration = (long) (MP4ParserUtil.getDuration(mFinalVideoPath) * 1000);
- mFinalClip.mTargetMills = mFinalClip.mDuration;
- return mFinalVideoPath;
- } catch (Exception ex) {
- return null;
- }
- }
3.拼接三段视频代码
- public boolean newClipMethod(String dstFile,String srcFile){
- try {
- double[] results = ClipMp4Util.startTrim(new File(dstFile),new File(srcFile),mTimeStart,mTimeEnd);
- if(results == null){
- return false;
- }
- Log.d("","newClipMethod-->results[0]-mTimeStart"+results[0]+" "+mTimeStart/1000);
- Log.d("","newClipMethod-->mTimeEnd-results[1]"+mTimeEnd/1000+" "+results[1]);
- //下面是短截取然后拼接的逻辑
- if(results[0]-mTimeStart/1000>GAP){
- String startMp4 = <span style="font-family: Arial, Helvetica, sans-serif;">getMp4ByFFmpeg(</span><span style="font-family: Arial, Helvetica, sans-serif;">mTimeStart,results[0]*1000,begin);</span>
- }
- if(mTimeEnd/1000-results[1]>GAP){
- String endMp4 = <span style="font-family: Arial, Helvetica, sans-serif;">getMp4ByCode(</span><span style="font-family: Arial, Helvetica, sans-serif;">results[1]*吧1000,mTimeEnd,end);</span>
- }
- String[] videos = new String[3];
- videos[0] = begin;
- videos[1] = dst;
- videos[2] = end;
- appendVideo(videos);
- } catch (Exception e) {
- //如果不是同一格式的视频,这里合成会报错,直接返回中间视频.所以长视频选取长误差的方式,前后都多截取一段
- Log.d("","new Method exception-->"+e);
- e.printStackTrace();
- }
- return true;
- }
相关工程后续会上传。
1.点击下载工程1
Android(java方法)上实现mp4的分割和拼接 (二)的更多相关文章
- Android(java方法)上实现mp4的分割和拼接 (一)
最近正在处理android上的mp4切割问题.学习了很多mp4的知识,mp4文件按照编码类型,分为mpeg-4,avc这两种:这两种类型的mp4在后面的处理中会有不同的地方. 在Android系 ...
- Android Studio NDK开发-JNI调用Java方法
相对于NDK来说SDK里面有更多API可以调用,有时候我们在做NDK开发的时候,需要在JNI直接Java中的方法和变量,比如callback,系统信息等.... 如何在JNI中调用Java方法呢?就需 ...
- Unity调用Android Studio中的Java方法
1. 新建Unity项目: 2. Android Studio中新建EmptyActivity: 3. 新建安卓项目时记住最小版本号: 4. 将左侧项目文件浏览面板切换到Project项下,在本项根节 ...
- Oracle调用Java方法(上)如何使用LoadJava命令和如何将简单的Jar包封装成Oracle方法
最近在工作中遇到了遇到了一个需求需要将TIPTOP中的数据导出成XML并上传到FTP主机中,但是4GL这方面的文档比较少最终决定使用Oracle调用Java的方法,在使用的过程中发现有很多的坑,大部分 ...
- 068 01 Android 零基础入门 01 Java基础语法 08 Java方法 06 参数传递问题——基本数据类型传值
068 01 Android 零基础入门 01 Java基础语法 08 Java方法 06 参数传递问题--基本数据类型传值 本文知识点:参数传递问题--基本数据类型传值 说明:因为时间紧张,本人写博 ...
- 067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数
067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数 本文知识点:数组作为方法参数 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...
- 066 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参有返回值方法
066 01 Android 零基础入门 01 Java基础语法 08 Java方法 04 带参有返回值方法 本文知识点:带参有返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...
- Android进程so注入Hook java方法
本文博客链接:http://blog.csdn.net/qq1084283172/article/details/53769331 Andorid的Hook方式比较多,现在来学习下,基于Android ...
- maven project中,在main方法上右键Run as Java Application时,提示错误:找不到或无法加载主类XXX.XXXX.XXX
新建了一个maven project项目,经过一大堆的修改操作之后,突然发现在main方法上右键运行时,竟然提示:错误:找不到或无法加载主类xxx.xxx.xxx可能原因1.eclipse出问题了,在 ...
随机推荐
- 设置eclipse中的${user}
打开eclipse根目录找到eclipse.ini文件增加初始配置: -Duser.name=snzigod@hotmail.com 重启eclipse后${user}变量的值就变成了snzigod@ ...
- HDU 5510 Bazinga KMP
题意: 给\(n(1 \leq n \leq 500)\)个字符串,求一个最大的\(i\),使得存在一个\(S_{j}\)不是\(S_i\)的子串. 分析: 维护两个指针\(l,r\) 那么有两种情况 ...
- 小米手机安装 charles 证书,提示“没有可安装的证书”
错误环境: 1. Mac 下的 charles 2. 小米手机(MI 6 MUI 9.6 ,Android 8.x) 错误提示: 安装 charles 证书时,提示“没有可安装的证书” 解决方法: 1 ...
- [POJ 1000] A+B Problem 经典水题 C++解题报告 JAVA解题报告
A+B Problem Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 311263 Accepted: 1713 ...
- Wannafly挑战赛5
珂朵莉与宇宙 时间限制:C/C++ 2秒,其他语言4秒空间限制:C/C++ 65536K,其他语言131072K64bit IO Format: %lld 题目描述 星神是来自宇宙的 所以珂朵莉也是吧 ...
- 九度oj 题目1007:奥运排序问题
九度oj 题目1007:奥运排序问题 恢复 题目描述: 按要求,给国家进行排名. 输入: 有多组数据. 第一行给出国家数N,要求排名的国家数M,国家号 ...
- BZOJ4010 [HNOI2015]菜肴制作 【拓扑排序 + 贪心】
题目 知名美食家小 A被邀请至ATM 大酒店,为其品评菜肴. ATM 酒店为小 A 准备了 N 道菜肴,酒店按照为菜肴预估的质量从高到低给予 1到N的顺序编号,预估质量最高的菜肴编号为1.由于菜肴之间 ...
- Android ARM指令学习
在逆向分析Android APK的时候,往往需要分析它的.so文件.这个.so文件就是Linux的动态链接库,只不过是在ARM-cpu下编译的.所以学习Android下的ARM指令很重要.目前,市面上 ...
- Java Socket IO(BIO、NIO)
总结下Java socket IO.首先是各种IO的定义,这个定义似乎也是众说纷纭.我按照stackoverflow上面的解释: IO有两种分法:按照阻塞或者按照同步.按照阻塞,有阻塞IO和非阻塞IO ...
- xsy 1845 - GCD
from NOIP2016模拟题34 Description 给定一个长度\(n\le 10^6\)的序列, 给定\(A, B\) 给出一个序列,要求你通过如下两个操作使得序列中所有数的最大公约数大于 ...