我正在尝试创建一个演示文稿,它将mp4文件作为输入,并在00:01到00:10之间将其修剪为10秒
我正在使用mp4parser来完成我的任务。
compile 'com.googlecode.mp4parser:isoparser:1.1.18'
我的活动中有以下代码:
MainActivity.java:
import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.MediaController;
import android.widget.VideoView;
import com.coremedia.iso.boxes.CompositionTimeToSample;
import com.coremedia.iso.boxes.Container;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity {
@Bind(R.id.videoView)
VideoView mVideoView;
Uri uri;
String TAG = getClass().getSimpleName();
private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
long currentSample = 0;
double currentTime = 0;
for (int i = 0; i < track.getCompositionTimeEntries().size(); i++) {
CompositionTimeToSample.Entry entry = track.getCompositionTimeEntries().get(i);
for (int j = 0; j < entry.getCount(); j++) {
if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
// samples always start with 1 but we start with zero therefore +1
timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime;
}
currentTime += (double) entry.getCount() / (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
}
double previous = 0;
for (double timeOfSyncSample : timeOfSyncSamples) {
if (timeOfSyncSample > cutHere) {
if (next) {
return timeOfSyncSample;
} else {
return previous;
}
}
previous = timeOfSyncSample;
}
return timeOfSyncSamples[timeOfSyncSamples.length - 1];
}
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @author paulburke
*/
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isStoragePermissionGranted()) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
new TrimAsyncTask(MainActivity.this).execute();
}
}
});
}
public boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG, "Permission is granted");
return true;
} else {
Log.v(TAG, "Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
} else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG, "Permission is granted");
return true;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.v(TAG, "Permission: " + permissions[0] + "was " + grantResults[0]);
//resume tasks needing this permission
new TrimAsyncTask(this).execute();
}
}
public void Mp4CutProcessBegin(Context context) {
try {
File sdCard = Environment.getExternalStorageDirectory();
Movie movie = MovieCreator.build(getPath(context, uri));
List<Track> tracks = movie.getTracks();
movie.setTracks(new LinkedList<Track>());
// remove all tracks we will create new tracks from the old
double startTime1 = 1000000; // What Time Should i set here and in which unit of time.
double endTime1 = 10000000; // What Time Should i set here and in which unit of time.
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
Log.i(TAG, "Tracks: " + String.valueOf(tracks.size()));
for (Track track : tracks) {
if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
if (timeCorrected) {
// This exception here could be a false positive in case we have multiple tracks
// with sync samples at exactly the same positions. E.g. a single movie containing
// multiple qualities of the same video (Microsoft Smooth Streaming file)
throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported.");
}
startTime1 = correctTimeToSyncSample(track, startTime1, false);
endTime1 = correctTimeToSyncSample(track, endTime1, true);
timeCorrected = true;
}
}
for (Track track : tracks) {
long currentSample = 0;
double currentTime = 0;
double lastTime = -1;
long startSample1 = -1;
long endSample1 = -1;
for (int i = 0; i < track.getSampleDurations().length; i++) {
long delta = track.getSampleDurations()[i];
if (currentTime > lastTime && currentTime <= startTime1) {
// current sample is still before the new starttime
startSample1 = currentSample;
}
if (currentTime > lastTime && currentTime <= endTime1) {
// current sample is after the new start time and still before the new endtime
endSample1 = currentSample;
}
lastTime = currentTime;
currentTime += (double) delta / (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
movie.addTrack(new AppendTrack(new CroppedTrack(track, startSample1, endSample1)));
}
long start1 = System.currentTimeMillis();
Container out = new DefaultMp4Builder().build(movie);
long start2 = System.currentTimeMillis();
File output = new File(sdCard + "/output.mp4");
if (output.exists()) {
output.delete();
}
FileOutputStream fos = new FileOutputStream(sdCard + "/output.mp4"); //String.format("output-%f-%f.mp4", startTime1, endTime1)
FileChannel fc = fos.getChannel();
out.writeContainer(fc);
fc.close();
fos.close();
long start3 = System.currentTimeMillis();
System.err.println("Building IsoFile took : " + (start2 - start1) + "ms");
System.err.println("Writing IsoFile took : " + (start3 - start2) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
@OnClick(R.id.buttonLoad)
public void load() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("video/*");
startActivityForResult(Intent.createChooser(i, "Pick a photo"), 1);
}
public void loadVideo(Uri uri) {
mVideoView.setMediaController(new MediaController(this));
mVideoView.setVideoURI(uri);
mVideoView.requestFocus();
}
@OnClick(R.id.buttonPlay)
public void startVideo() {
mVideoView.start();
}
@OnClick(R.id.buttonStop)
public void stopVideo() {
mVideoView.stopPlayback();
loadVideo(uri);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
uri = data.getData();
loadVideo(uri);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public class TrimAsyncTask extends AsyncTask<Void, Void, Void> {
Context mContext;
public TrimAsyncTask(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(Void... params) {
Mp4CutProcessBegin(mContext);
return null;
}
}
}
从Log.x()
生成的日志:
03-18 10:27:48.717 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.717 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.719 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.735 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of tkhd
03-18 10:27:48.736 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.736 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of tkhd
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stco
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stsc
03-18 10:27:48.737 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stsz
03-18 10:27:48.749 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of hdlr
03-18 10:27:48.750 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stts
03-18 10:27:48.750 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stss
03-18 10:27:48.758 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of mdhd
03-18 10:27:48.761 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of elst
03-18 10:27:48.762 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of mvhd
03-18 10:27:48.769 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stco
03-18 10:27:48.769 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stsc
03-18 10:27:48.769 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stsz
03-18 10:27:48.778 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of hdlr
03-18 10:27:48.778 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of stts
03-18 10:27:48.779 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of mdhd
03-18 10:27:48.780 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: AbstractBox:parsing details of elst
03-18 10:27:48.781 26598-26598/com.letsnurture.ln_202.videotrimdemo I/MainActivity: Tracks: 2
03-18 10:27:48.784 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:Creating movie Movie{ track_1 (vide) track_2 (soun) }
03-18 10:27:48.786 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:Calculating chunk offsets for track_1
03-18 10:27:48.786 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:done with stbl for track_1
03-18 10:27:48.786 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:done with trak for track_1
03-18 10:27:48.787 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:done with stbl for track_2
03-18 10:27:48.787 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:done with trak for track_2
03-18 10:27:48.789 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:About to create mdat
03-18 10:27:48.789 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:mdat crated
03-18 10:27:48.790 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.790 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:48.793 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:potentially expensive size() call
03-18 10:27:48.793 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: LazyList:blowup running
03-18 10:27:49.067 26598-26598/com.letsnurture.ln_202.videotrimdemo D/isoparser: DefaultMp4Builder:About to write 0
03-18 10:27:49.068 26598-26598/com.letsnurture.ln_202.videotrimdemo W/System.err: Building IsoFile took : 12ms
03-18 10:27:49.068 26598-26598/com.letsnurture.ln_202.videotrimdemo W/System.err: Writing IsoFile took : 272ms
问题:
我的output.mp4
文件产生了4KB,在播放时没有播放任何内容。只要黑屏和播放器一开始就会停止。
我是否给出了错误的开始时间和结束时间?
请帮我解决这个问题。 感谢。
答案 0 :(得分:6)
好的,看起来你好了 this 或this 代码作为基础(实际上,mp4parser存储库中有太多非工作示例)。 不幸的是,这段代码已经过时了,因为mp4parser有一些重大变化;所以,您应该使用旧版本的mp4parser(我猜0.9.x)或修复代码 - this thread会有所帮助。
我写了一个小类,用于剪切mp4文件(使用mp4parser 1.1.18):
import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.MovieHeaderBox;
import com.googlecode.mp4parser.FileDataSourceImpl;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
import com.googlecode.mp4parser.util.Matrix;
import com.googlecode.mp4parser.util.Path;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* @author Nikolai Doronin {@literal <lassana.nd@gmail.com>}.
* @since 3/23/16.
*/
public class Mp4Cutter2 {
public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException {
FileDataSourceImpl file = new FileDataSourceImpl(src);
Movie movie = MovieCreator.build(file);
// remove all tracks we will create new tracks from the old
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) {
// This exception here could be a false positive in case we have multiple tracks
// with sync samples at exactly the same positions. E.g. a single movie containing
// multiple qualities of the same video (Microsoft Smooth Streaming file)
throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported.");
}
startTime = correctTimeToSyncSample(track, startTime, false);
endTime = correctTimeToSyncSample(track, endTime, true);
timeCorrected = true;
}
}
for (Track track : tracks) {
long currentSample = 0;
double currentTime = 0;
long startSample = -1;
long endSample = -1;
for (int i = 0; i < track.getSampleDurations().length; i++) {
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) track.getSampleDurations()[i] / (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
movie.addTrack(new CroppedTrack(track, startSample, endSample));
}
Container out = new DefaultMp4Builder().build(movie);
MovieHeaderBox mvhd = Path.getPath(out, "moov/mvhd");
mvhd.setMatrix(Matrix.ROTATE_180);
if (!dst.exists()) {
dst.createNewFile();
}
FileOutputStream fos = new FileOutputStream(dst);
WritableByteChannel fc = fos.getChannel();
try {
out.writeContainer(fc);
} finally {
fc.close();
fos.close();
file.close();
}
file.close();
}
private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
long currentSample = 0;
double currentTime = 0;
for (int i = 0; i < track.getSampleDurations().length; i++) {
long delta = track.getSampleDurations()[i];
if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime;
}
currentTime += (double) delta / (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
double previous = 0;
for (double timeOfSyncSample : timeOfSyncSamples) {
if (timeOfSyncSample > cutHere) {
if (next) {
return timeOfSyncSample;
} else {
return previous;
}
}
previous = timeOfSyncSample;
}
return timeOfSyncSamples[timeOfSyncSamples.length - 1];
}
}
用法:
final File source = new File("/home/user/downloads/1.mp4");
final File output = new File("/home/user/downloads/1.mp4" + System.currentTimeMillis() + ".mp4");
final int start = 24*1000;
final int end = 37*1000;
Mp4Cutter2.startTrim(source, output, start, end);
我没有仔细查看您的代码,但我认为问题出在correctTimeToSyncSample
方法中。
<强>限制强>
我应该注意,使用mp4parser可能无法将视频精确地从1s切换到10s。在测试视频中,correctTimeToSyncSample
已将startTime
和endTime
从24,37
更改为21.12, 43.44
。
您可以跳过此方法并始终使用给定的起始端值,但您应该准备好在输出视频中出现一些故障,因为h264和类似编解码器中特定帧的内容可能取决于previos帧/帧的内容。请看一下:original video,clipped video。
如果你真的需要控制开始和停止值,你应该考虑ffmpeg。
答案 1 :(得分:0)
您可以使用ffmpeg以多种方式播放视频文件。
https://github.com/WritingMinds/ffmpeg-android-java
只需要准备所需的命令
例如ffmpeg -i movie.mp4 -ss 00:00:03 -t 00:00:08 -async 1 cut.mp4
使用ffmpeg处理视频文件非常容易。