Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1567729
  • 博文数量: 113
  • 博客积分: 3526
  • 博客等级: 中校
  • 技术积分: 1815
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-08 09:46
个人简介

记录总结自己的工作

文章分类

全部博文(113)

文章存档

2015年(19)

2014年(10)

2013年(6)

2012年(16)

2011年(24)

2010年(21)

2009年(17)

分类: Android平台

2014-02-20 14:23:05

      最近项目有个需求要对录制的视频进行分割,查了很多资料,看到ffmpeg可以对视频进行分割。上网找到别人基于android的开源ffmpeg,终于编译成功ffmpeg.so。但是要使用的话还要查ffmpeg的api,并且写jni的调用接口,非常麻烦。偶然情况下发现了开源软件mp4parser:  一款非常棒的开源软件,可以对视频进行分割、组合等操作,而且使用起来非常简单。通过svn对其下载后可以看到里面带着视频分割的例子,但是是用java实现,将其稍微修改一下就可以用在Android上了。

      首先将例子中的代码修改为一个工具类,通过接口传进视频文件的路径和截取视频的开始、结束时间。需要注意的是,如果传的开始时间是10s,视频一般不会刚好是从10s开始的,要根据视频的关键帧做一下调整。截取出来的视频会放到存储卡的Clip目录下。代码如下:

点击(此处)折叠或打开

  1. package com.example.mp4clip;

  2. import java.io.File;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.nio.channels.FileChannel;
  6. import java.util.Arrays;
  7. import java.util.LinkedList;
  8. import java.util.List;

  9. import android.os.Environment;
  10. import android.util.Log;

  11. import com.coremedia.iso.boxes.Container;
  12. import com.googlecode.mp4parser.authoring.Movie;
  13. import com.googlecode.mp4parser.authoring.Track;
  14. import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
  15. import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
  16. import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;

  17. public class ClipUtil {
  18.     private static final String TAG = "ClipUtil";

  19.     /**
  20.      * 截取指定时间段的视频
  21.      * @param path 视频的路径
  22.      * @param begin 需要截取的开始时间
  23.      * @param end 截取的结束时间
  24.      * @throws IOException
  25.      */
  26.     public static void clipVideo(String path, double begin, double end)
  27.             throws IOException {
  28.         File mSdCardDir = Environment.getExternalStorageDirectory();
  29.         File f = new File(mSdCardDir.getAbsolutePath() + File.separator
  30.                 + Util.SAVE_PATH);
  31.         if (!f.exists()) {
  32.             f.mkdir();
  33.         }
  34.         // Movie movie = new MovieCreator().build(new
  35.         // RandomAccessFile("/home/sannies/suckerpunch-distantplanet_h1080p/suckerpunch-distantplanet_h1080p.mov",
  36.         // "r").getChannel());
  37.         Movie movie = MovieCreator.build(path);

  38.         List<Track> tracks = movie.getTracks();
  39.         movie.setTracks(new LinkedList<Track>());
  40.         // remove all tracks we will create new tracks from the old

  41.         double startTime1 = begin;
  42.         double endTime1 = end;
  43.         // double startTime2 = 30;
  44.         // double endTime2 = 40;

  45.         boolean timeCorrected = false;

  46.         // Here we try to find a track that has sync samples. Since we can only
  47.         // start decoding
  48.         // at such a sample we SHOULD make sure that the start of the new
  49.         // fragment is exactly
  50.         // such a frame
  51.         for (Track track : tracks) {
  52.             if (track.getSyncSamples() != null
  53.                     && track.getSyncSamples().length > 0) {
  54.                 if (timeCorrected) {
  55.                     // This exception here could be a false positive in case we
  56.                     // have multiple tracks
  57.                     // with sync samples at exactly the same positions. E.g. a
  58.                     // single movie containing
  59.                     // multiple qualities of the same video (Microsoft Smooth
  60.                     // Streaming file)
  61.                     Log.e(TAG,
  62.                             "The startTime has already been corrected by another track with SyncSample. Not Supported.");
  63.                     throw new RuntimeException(
  64.                             "The startTime has already been corrected by another track with SyncSample. Not Supported.");
  65.                 }
  66.                 startTime1 = correctTimeToSyncSample(track, startTime1, false);
  67.                 endTime1 = correctTimeToSyncSample(track, endTime1, true);
  68.                 // startTime2 = correctTimeToSyncSample(track, startTime2,
  69.                 // false);
  70.                 // endTime2 = correctTimeToSyncSample(track, endTime2, true);
  71.                 timeCorrected = true;
  72.             }
  73.         }

  74.         for (Track track : tracks) {
  75.             long currentSample = 0;
  76.             double currentTime = 0;
  77.             double lastTime = 0;
  78.             long startSample1 = -1;
  79.             long endSample1 = -1;
  80.             // long startSample2 = -1;
  81.             // long endSample2 = -1;

  82.             for (int i = 0; i < track.getSampleDurations().length; i++) {
  83.                 long delta = track.getSampleDurations()[i];

  84.                 if (currentTime > lastTime && currentTime <= startTime1) {
  85.                     // current sample is still before the new starttime
  86.                     startSample1 = currentSample;
  87.                 }
  88.                 if (currentTime > lastTime && currentTime <= endTime1) {
  89.                     // current sample is after the new start time and still
  90.                     // before the new endtime
  91.                     endSample1 = currentSample;
  92.                 }
  93.                 // if (currentTime > lastTime && currentTime <= startTime2) {
  94.                 // // current sample is still before the new starttime
  95.                 // startSample2 = currentSample;
  96.                 // }
  97.                 // if (currentTime > lastTime && currentTime <= endTime2) {
  98.                 // // current sample is after the new start time and still
  99.                 // before the new endtime
  100.                 // endSample2 = currentSample;
  101.                 // }
  102.                 lastTime = currentTime;
  103.                 currentTime += (double) delta
  104.                         / (double) track.getTrackMetaData().getTimescale();
  105.                 currentSample++;
  106.             }
  107.             movie.addTrack(new CroppedTrack(track, startSample1, endSample1));// new
  108.                                                                                 // AppendTrack(new
  109.                                                                                 // CroppedTrack(track,
  110.                                                                                 // startSample1,
  111.                                                                                 // endSample1),
  112.                                                                                 // new
  113.                                                                                 // CroppedTrack(track,
  114.                                                                                 // startSample2,
  115.                                                                                 // endSample2)));
  116.         }
  117.         long start1 = System.currentTimeMillis();
  118.         Container out = new DefaultMp4Builder().build(movie);
  119.         long start2 = System.currentTimeMillis();
  120.         FileOutputStream fos = new FileOutputStream(f.getAbsolutePath()
  121.                 + File.separator
  122.                 + String.format("output-%f-%f.mp4", startTime1, endTime1));
  123.         FileChannel fc = fos.getChannel();
  124.         out.writeContainer(fc);

  125.         fc.close();
  126.         fos.close();
  127.         long start3 = System.currentTimeMillis();
  128.         Log.e(TAG, "Building IsoFile took : " + (start2 - start1) + "ms");
  129.         Log.e(TAG, "Writing IsoFile took : " + (start3 - start2) + "ms");
  130.         Log.e(TAG,
  131.                 "Writing IsoFile speed : "
  132.                         + (new File(String.format("output-%f-%f.mp4",
  133.                                 startTime1, endTime1)).length()
  134.                                 / (start3 - start2) / 1000) + "MB/s");
  135.     }

  136.     private static double correctTimeToSyncSample(Track track, double cutHere,
  137.             boolean next) {
  138.         double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
  139.         long currentSample = 0;
  140.         double currentTime = 0;
  141.         for (int i = 0; i < track.getSampleDurations().length; i++) {
  142.             long delta = track.getSampleDurations()[i];

  143.             if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
  144.                 // samples always start with 1 but we start with zero therefore
  145.                 // +1
  146.                 timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(),
  147.                         currentSample + 1)] = currentTime;
  148.             }
  149.             currentTime += (double) delta
  150.                     / (double) track.getTrackMetaData().getTimescale();
  151.             currentSample++;

  152.         }
  153.         double previous = 0;
  154.         for (double timeOfSyncSample : timeOfSyncSamples) {
  155.             if (timeOfSyncSample > cutHere) {
  156.                 if (next) {
  157.                     return timeOfSyncSample;
  158.                 } else {
  159.                     return previous;
  160.                 }
  161.             }
  162.             previous = timeOfSyncSample;
  163.         }
  164.         return timeOfSyncSamples[timeOfSyncSamples.length - 1];
  165.     }

  166. }
     

         有了工具类,下面就是增加一个操作界面了。我用一个列表列出所有的视频,点击视频后就会在后台截取出5s~15s总共10s的视频。当然也可以根据需要加上自己想要的开始结束时间,代码如下:



点击(此处)折叠或打开

  1. package com.example.mp4clip;

  2. import java.io.IOException;
  3. import java.lang.ref.SoftReference;

  4. import android.app.Activity;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.database.Cursor;
  8. import android.graphics.drawable.Drawable;
  9. import android.net.Uri;
  10. import android.os.Bundle;
  11. import android.os.Environment;
  12. import android.provider.MediaStore;
  13. import android.util.Log;
  14. import android.util.SparseArray;
  15. import android.view.Menu;
  16. import android.view.MenuItem;
  17. import android.view.View;
  18. import android.view.ViewGroup;
  19. import android.widget.AdapterView;
  20. import android.widget.AdapterView.OnItemClickListener;
  21. import android.widget.ImageView;
  22. import android.widget.ListView;
  23. import android.widget.SimpleCursorAdapter;
  24. import android.widget.TextView;
  25. import edu.mit.mobile.android.imagecache.ImageCache;
  26. import edu.mit.mobile.android.imagecache.ImageCache.OnImageLoadListener;

  27. public class MainActivity extends Activity implements OnItemClickListener,
  28.         OnImageLoadListener {

  29.     private static final String TAG = "MainActivity";
  30.     ListView mList;
  31.     private Cursor mCursor;
  32.     private final SparseArray<SoftReference<ImageView>> mImageViewsToLoad = new SparseArray<SoftReference<ImageView>>();
  33.     private ImageCache mCache;

  34.     @Override
  35.     protected void onCreate(Bundle savedInstanceState) {
  36.         super.onCreate(savedInstanceState);
  37.         setContentView(R.layout.activity_main);
  38.         mCache = ImageCache.getInstance(this);
  39.         mCache.registerOnImageLoadListener(this);
  40.         mList = (ListView) findViewById(R.id.list);
  41.         mList.setOnItemClickListener(this);
  42.         mCursor = getContentResolver().query(
  43.                 MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null,
  44.                 MediaStore.Video.Media.DATE_MODIFIED + " desc");
  45.         SimpleCursorAdapter adapter = new videoListAdapter(this,
  46.                 R.layout.video_listitem, mCursor,
  47.                 new String[] { MediaStore.Video.Media.TITLE },
  48.                 new int[] { R.id.video_title });
  49.         mList.setAdapter(adapter);
  50.     }

  51.     @Override
  52.     public boolean onCreateOptionsMenu(Menu menu) {
  53.         getMenuInflater().inflate(R.menu.main, menu);
  54.         return true;
  55.     }

  56.     public boolean onOptionsItemSelected(MenuItem item) {
  57.         // 扫描新多媒体文件,添加到数据库中
  58.         sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
  59.                 Uri.parse("file://"
  60.                         + Environment.getExternalStorageDirectory()
  61.                                 .getAbsolutePath())));
  62.         return false;
  63.     }

  64.     @Override
  65.     public void onItemClick(AdapterView<?> parent, View view, int position,
  66.             long id) {
  67.         if (mCursor.moveToPosition(position)) {
  68.             int index = -1;
  69.             index = mCursor.getColumnIndex(MediaStore.Video.Media.DATA);
  70.             String path = null;
  71.             if (index >= 0) {
  72.                 path = mCursor.getString(index);
  73.                 try {
  74.                     ClipUtil.clipVideo(path, 5, 15);
  75.                 } catch (IOException e) {
  76.                     // TODO Auto-generated catch block
  77.                     e.printStackTrace();
  78.                 }
  79.             }
  80.         }

  81.     }

  82.     private static final class ViewHolder {
  83.         /** 视频名称 */
  84.         TextView titleView;
  85.         /** 视频时长 */
  86.         TextView durationView;
  87.         /** 文件大小 */
  88.         TextView sizeView;
  89.     }

  90.     private class videoListAdapter extends SimpleCursorAdapter {

  91.         /*
  92.          * constructor.
  93.          */
  94.         public videoListAdapter(Context context, int layout, Cursor c,
  95.                 String[] from, int[] to) {
  96.             super(context, layout, c, from, to);
  97.         }

  98.         @Override
  99.         public int getCount() {
  100.             return super.getCount();
  101.         }

  102.         @Override
  103.         public Object getItem(int position) {
  104.             return super.getItem(position);
  105.         }

  106.         @Override
  107.         public long getItemId(int position) {
  108.             return super.getItemId(position);
  109.         }

  110.         @Override
  111.         public View getView(int position, View convertView, ViewGroup parent) {
  112.             View view = super.getView(position, convertView, parent);
  113.             Cursor cursor = getCursor();
  114.             cursor.moveToPosition(position);
  115.             ViewHolder holder = (ViewHolder) view.getTag();
  116.             if (holder == null) {
  117.                 holder = new ViewHolder();
  118.                 holder.titleView = (TextView) view
  119.                         .findViewById(R.id.video_title);
  120.                 holder.durationView = (TextView) view
  121.                         .findViewById(R.id.video_duration);
  122.                 holder.sizeView = (TextView) view.findViewById(R.id.video_size);
  123.             }
  124.             view.setTag(holder);
  125.             final ImageView iv = (ImageView) view.findViewById(R.id.thumbnail);
  126.             int index = -1;
  127.             index = mCursor.getColumnIndex(MediaStore.Video.Media.DATA);
  128.             String path = null;
  129.             if (index >= 0) {
  130.                 path = mCursor.getString(index);
  131.                 try {
  132.                     Drawable draw = mCache.loadImage(position, Uri.parse(path),
  133.                             120, 120);
  134.                     if (draw != null) {
  135.                         iv.setBackground(draw);
  136.                     } else {
  137.                         mImageViewsToLoad.put(position,
  138.                                 new SoftReference<ImageView>(iv));
  139.                     }
  140.                 } catch (IOException e) {
  141.                     e.printStackTrace();
  142.                 }
  143.             }
  144.             index = -1;
  145.             index = cursor.getColumnIndex(MediaStore.Video.Media.TITLE);
  146.             String title = null;
  147.             if (index >= 0) {
  148.                 title = cursor.getString(index);
  149.                 holder.titleView.setText(title);
  150.             }
  151.             index = -1;
  152.             index = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
  153.             int duration;
  154.             if (index >= 0) {
  155.                 duration = cursor.getInt(index);
  156.                 holder.durationView.setText(Util.durationFormat(duration));
  157.             }
  158.             index = -1;
  159.             index = cursor.getColumnIndex(MediaStore.Video.Media.SIZE);
  160.             long size;
  161.             if (index >= 0) {
  162.                 size = cursor.getLong(index);
  163.                 holder.sizeView.setText(Util.sizeFormat(size));
  164.             }
  165.             return view;

  166.         }

  167.     }

  168.     @Override
  169.     public void onImageLoaded(int id, Uri imageUri, Drawable image) {
  170.         Log.d(TAG, "onImageLoaded:" + id);
  171.         final SoftReference<ImageView> ivRef = mImageViewsToLoad.get(id);
  172.         if (ivRef == null) {
  173.             Log.d(TAG, "ivRef=null");
  174.             return;
  175.         }
  176.         final ImageView iv = ivRef.get();
  177.         if (iv == null) {
  178.             Log.d(TAG, "ivRef=null");
  179.             mImageViewsToLoad.remove(id);
  180.             return;
  181.         }
  182.         iv.setBackground(image);
  183.     }
  184. }







阅读(19411) | 评论(33) | 转发(2) |
给主人留下些什么吧!~~

quruti2015-07-02 19:00:36

云少嘎嘎嘎:没有啊,你使用github上的demo最好也使用上面最新的jar包。

额,好像有点奇怪,我检查了一下目录,文件已经生成了,不过很奇怪的是这文件在log里面打印出来长度为0,而且重新执行程序的时候,剪切出来的MP4没有被扫描到list里面,博主有这中情况吗?

回复 | 举报

quruti2015-07-02 18:13:58

云少嘎嘎嘎:没有啊,你使用github上的demo最好也使用上面最新的jar包。

请教一下博主,我用了你的上面的代码,不过运行之后的输出文件是空的:output-XXXXXX.mp4长度为0.之前的Building和writing倒是有ms时间,并且随着不同的输入值,这两个时间也不断在变化,不过就是输出文件长度是0.请问这个可能是什么问题?

回复 | 举报

云少嘎嘎嘎2015-07-02 15:23:48

quruti:谢谢博主的快速回复,还有一个问题,我导入git上https://github.com/sannies/mp4parser的那个demo的时候,发现缺少了com.googlecode.mp4parser.FileChannel这个包,我用的jar包是isoviewer-1.0-RC-35.jar,请教作者有没有遇到过这个问题?

没有啊,你使用github上的demo最好也使用上面最新的jar包。

回复 | 举报

quruti2015-07-02 09:59:54

云少嘎嘎嘎:前面仅仅是个变量存储路径,自己定义就可以了;后面是格式化时间的方法,如果仅仅是为了运行demo的话都可以去掉不用的。

谢谢博主的快速回复,还有一个问题,我导入git上https://github.com/sannies/mp4parser的那个demo的时候,发现缺少了com.googlecode.mp4parser.FileChannel这个包,我用的jar包是isoviewer-1.0-RC-35.jar,请教作者有没有遇到过这个问题?

回复 | 举报

云少嘎嘎嘎2015-07-02 09:22:25

quruti:请教作者我在复制代码时遇到Util.PATH, Util.durationFormat这里都报错,请作者能不能指点一下?

前面仅仅是个变量存储路径,自己定义就可以了;后面是格式化时间的方法,如果仅仅是为了运行demo的话都可以去掉不用的。

回复 | 举报