相关文章推荐
暴走的海龟  ·  easyui ...·  1 年前    · 
兴奋的蘑菇  ·  [路由器] ...·  1 年前    · 

最近项目有个需求要对录制的视频进行分割,查了很多资料,看到ffmpeg可以对视频进行分割。上网找到别人基于android的开源ffmpeg,终于编译成功ffmpeg.so。但是要使用的话还要查ffmpeg的api,并且写jni的调用接口,非常麻烦。偶然情况下发现了开源软件mp4parser: http://code.google.com/p/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. }