这节谈一下如何在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的开头和结尾时刻

**/

  1. public static double[] startTrim(File src, File dst, int startMs, int endMs) throws IOException {
  2. Movie movie = MovieCreator.build(src.getAbsolutePath());
  3. List<Track> tracks = movie.getTracks();
  4. movie.setTracks(new LinkedList<Track>());
  5. double startTime = startMs/1000;
  6. double endTime = endMs/1000;
  7. boolean timeCorrected = false;
  8. // Here we try to find a track that has sync samples. Since we can only start decoding
  9. // at such a sample we SHOULD make sure that the start of the new fragment is exactly
  10. // such a frame
  11. for (Track track : tracks) {
  12. if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
  13. if (timeCorrected) {
  14. throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported.");
  15. }
  16. //true,false表示短截取;false,true表示长截取
  17. startTime = correctTimeToSyncSample(track, startTime, true);
  18. endTime = correctTimeToSyncSample(track, endTime, false);
  19. timeCorrected = true;
  20. }
  21. }
  22. int x = 0;
  23. for (Track track : tracks) {
  24. long currentSample = 0;
  25. double currentTime = 0;
  26. long startSample = -1;
  27. long endSample = -1;
  28. x++;
  29. for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
  30. TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
  31. for (int j = 0; j < entry.getCount(); j++) {
  32. // entry.getDelta() is the amount of time the current sample covers.
  33. if (currentTime <= startTime) {
  34. // current sample is still before the new starttime
  35. startSample = currentSample;
  36. }
  37. if (currentTime <= endTime) {
  38. // current sample is after the new start time and still before the new endtime
  39. endSample = currentSample;
  40. } else {
  41. // current sample is after the end of the cropped video
  42. break;
  43. }
  44. currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
  45. currentSample++;
  46. }
  47. }
  48. movie.addTrack(new CroppedTrack(track, startSample, endSample));
  49. break;
  50. }
  51. Container container = new DefaultMp4Builder().build(movie);
  52. if (!dst.exists()) {
  53. dst.createNewFile();
  54. }
  55. FileOutputStream fos = new FileOutputStream(dst);
  56. FileChannel fc = fos.getChannel();
  57. container.writeContainer(fc);
  58. fc.close();
  59. fos.close();
  60. double[] doubleArray = new double[2] ;
  61. doubleArray[0] = startTime;
  62. doubleArray[1] = endTime;
  63. return doubleArray;
  64. }

2.ffmpeg切割方法,需要jni实现。稍后补充

  1. public String getMp4ByFFmpeg(double mTimeStart,double mTimeEnd,String videoPath){
  2. try{
  3. String mFinalVideoPath = videoPath;
  4. int audioChannels = 2;
  5. FFmpegRecorder recorder = new FFmpegRecorder(
  6. mFinalVideoPath, RecorderConfig.TARGET_VIDEO_WIDTH,
  7. RecorderConfig.TARGET_VIDEO_HEIGHT, audioChannels);
  8. RecorderConfig.setRecorderConfig(recorder, RecorderConfig.CONFIG_TYPE_MPEG4_HIGH);
  9. int totalFrames = 0;
  10. FFmpegGrabber grabber = FFmpegGrabber.createDefault(mPath);
  11. grabber.setSquareSize(RecorderConfig.TARGET_VIDEO_WIDTH);
  12. int degree = VideoFileUtil.getRotate(mPath);
  13. grabber.setOrientation(degree);
  14. grabber.start();
  15. if (mTimeStart > 0) {
  16. grabber.setTimestamp((long)mTimeStart);
  17. }
  18. totalFrames = grabber.getLengthInFrames();
  19. VideoClip mFinalClip = new VideoClip();
  20. mFinalClip.mIsFromLocal = true;
  21. mFinalClip.mHeight = RecorderConfig.TARGET_VIDEO_HEIGHT;
  22. mFinalClip.mWidth = RecorderConfig.TARGET_VIDEO_WIDTH;
  23. recorder.setAudioChannels(grabber.getAudioChannels());
  24. recorder.setSampleRate(grabber.getSampleRate());
  25. recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
  26. recorder.setFrameRate(FFmpegRecorder.DEFAULT_FRAME_RATE);
  27. recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4);
  28. recorder.start();
  29. mFinalClip.mOrientation = 0;
  30. mFinalClip.mFrameRate = (int) recorder.getFrameRate();
  31. mFinalClip.mSampleRate = recorder.getSampleRate();
  32. mFinalClip.mAudioBitrate = recorder.getAudioBitrate();
  33. mFinalClip.mAudioChannels = recorder.getAudioChannels();
  34. Frame grabbedFrame = new Frame();
  35. int j = 0;
  36. boolean videoTimeout = false;
  37. boolean audioTimeout = false;
  38. while (grabber.grabFrame(grabbedFrame)) {
  39. long i = grabber.getTimestamp();
  40. long k = grabber.getFrameNumber();
  41. if (videoTimeout && audioTimeout) {
  42. break;
  43. }
  44. if (grabbedFrame.hasVideoFrame()) {
  45. int progress = 100 * (int) (i - mTimeStart) / mTotalTimeSpan;
  46. publishProgress(progress);
  47. }
  48. if (i > mTimeEnd) {
  49. if (grabbedFrame.hasAudioFrame()) {
  50. audioTimeout = true;
  51. }
  52. if (grabbedFrame.hasVideoFrame()) {
  53. videoTimeout = true;
  54. }
  55. continue;
  56. }
  57. grabbedFrame.setTimeStamp((long)(i - mTimeStart));
  58. recorder.recordFrameNoException(grabbedFrame);
  59. SLog.v(TAG, "record image at {}, #{}", i, k);
  60. j++;
  61. }
  62. grabbedFrame.releaseNativeAllocation();
  63. grabber.stop();
  64. grabber.release();
  65. recorder.stop();
  66. recorder.release();
  67. mFinalClip.mClipPath = mFinalVideoPath;
  68. mFinalClip.mDuration = (long) (MP4ParserUtil.getDuration(mFinalVideoPath) * 1000);
  69. mFinalClip.mTargetMills = mFinalClip.mDuration;
  70. return mFinalVideoPath;
  71. } catch (Exception ex) {
  72. return null;
  73. }
  74. }

3.拼接三段视频代码

  1. public boolean newClipMethod(String dstFile,String srcFile){
  2. try {
  3. double[] results = ClipMp4Util.startTrim(new File(dstFile),new File(srcFile),mTimeStart,mTimeEnd);
  4. if(results == null){
  5. return false;
  6. }
  7. Log.d("","newClipMethod-->results[0]-mTimeStart"+results[0]+" "+mTimeStart/1000);
  8. Log.d("","newClipMethod-->mTimeEnd-results[1]"+mTimeEnd/1000+" "+results[1]);
  9. //下面是短截取然后拼接的逻辑
  10. if(results[0]-mTimeStart/1000>GAP){
  11. 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>
  12. }
  13. if(mTimeEnd/1000-results[1]>GAP){
  14. 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>
  15. }
  16. String[] videos = new String[3];
  17. videos[0] = begin;
  18. videos[1] = dst;
  19. videos[2] = end;
  20. appendVideo(videos);
  21. } catch (Exception e) {
  22. //如果不是同一格式的视频,这里合成会报错,直接返回中间视频.所以长视频选取长误差的方式,前后都多截取一段
  23. Log.d("","new Method exception-->"+e);
  24. e.printStackTrace();
  25. }
  26. return true;
  27. }

相关工程后续会上传。
         1.点击下载工程1

 

Android(java方法)上实现mp4的分割和拼接 (二)的更多相关文章

  1. Android(java方法)上实现mp4的分割和拼接 (一)

       最近正在处理android上的mp4切割问题.学习了很多mp4的知识,mp4文件按照编码类型,分为mpeg-4,avc这两种:这两种类型的mp4在后面的处理中会有不同的地方. 在Android系 ...

  2. Android Studio NDK开发-JNI调用Java方法

    相对于NDK来说SDK里面有更多API可以调用,有时候我们在做NDK开发的时候,需要在JNI直接Java中的方法和变量,比如callback,系统信息等.... 如何在JNI中调用Java方法呢?就需 ...

  3. Unity调用Android Studio中的Java方法

    1. 新建Unity项目: 2. Android Studio中新建EmptyActivity: 3. 新建安卓项目时记住最小版本号: 4. 将左侧项目文件浏览面板切换到Project项下,在本项根节 ...

  4. Oracle调用Java方法(上)如何使用LoadJava命令和如何将简单的Jar包封装成Oracle方法

    最近在工作中遇到了遇到了一个需求需要将TIPTOP中的数据导出成XML并上传到FTP主机中,但是4GL这方面的文档比较少最终决定使用Oracle调用Java的方法,在使用的过程中发现有很多的坑,大部分 ...

  5. 068 01 Android 零基础入门 01 Java基础语法 08 Java方法 06 参数传递问题——基本数据类型传值

    068 01 Android 零基础入门 01 Java基础语法 08 Java方法 06 参数传递问题--基本数据类型传值 本文知识点:参数传递问题--基本数据类型传值 说明:因为时间紧张,本人写博 ...

  6. 067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数

    067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数 本文知识点:数组作为方法参数 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  7. 066 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参有返回值方法

    066 01 Android 零基础入门 01 Java基础语法 08 Java方法 04 带参有返回值方法 本文知识点:带参有返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  8. Android进程so注入Hook java方法

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/53769331 Andorid的Hook方式比较多,现在来学习下,基于Android ...

  9. maven project中,在main方法上右键Run as Java Application时,提示错误:找不到或无法加载主类XXX.XXXX.XXX

    新建了一个maven project项目,经过一大堆的修改操作之后,突然发现在main方法上右键运行时,竟然提示:错误:找不到或无法加载主类xxx.xxx.xxx可能原因1.eclipse出问题了,在 ...

随机推荐

  1. emacs写cnblog博客

    emacs的版本 org-mode版本   参考链接: 用Emacs管理博客园博客   用emacs org-mode写cnblogs博客 用emacs org-mode写博客 & 发布到博客 ...

  2. PHP全栈开发

     DAY01_PHP基础第一天                 01.了解php  00:09:26 ★  02.php的开发环境准备  00:13:47 ★  03.人人都会编程  00:10:26 ...

  3. 如何安装mongodb.msi

    到MongoDB官网下载MongoDB软件:mongodb-win32-x86_64-2008plus-ssl-3.0.2-signed.msi, 放在想要安装的地方: 如:d:\MongoDB\ 2 ...

  4. linux 命令 笔记

    ftp添加用户步骤: 创建目录 sudo mkdir /home/www 为目录添加用户 sudo useradd -d /home/uftp -s /bin/bash uftp 添加用户权限 sud ...

  5. layui.code代码装饰器

    <!doctype html><html lang="en"><head> <meta charset="UTF-8" ...

  6. Kafka单机配置部署

    摘要:上节 学习了Kafka的理论知识,这里安装单机版以便后续的测试. 首先安装jdk 一.单机部署zk 1.1安装: tar -zxf zookeeper-3.4.10.tar.gz -C /opt ...

  7. 【bzoj3073】[Pa2011]Journeys 线段树优化建图+堆优化Dijkstra

    题目描述 Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路.N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a, ...

  8. 【Luogu】P2303Longge的问题(莫比乌斯反演)

    就让我这样的蒟蒻发一个简单易想的题解吧!!! 这题我一开始一看,woc这不是莫比乌斯反演么,推推推,推到杜教筛,输出结果一看不对 emmm回来仔细想想……woc推错了? 然后撕烤半天打了个暴力,A了 ...

  9. 扩展kmp--模板解析

    扩展kmp: 用于求串的各个后缀与原串的最长公共前缀的长度: 上图的是字符串自匹配的过程: 图一: 假设现在匹配到i-1了,开始求next [ i ] 的值,此时,k记录的是到目前为止匹配到的最远的位 ...

  10. BZOJ4517 [Sdoi2016]排列计数 【组合数 + dp】

    题目 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 i,则称 i 是稳定的.序列恰好有 m 个数是稳定的 满足条件的 ...