android:trim mp4视频

时间:2016-03-18 05:10:30

标签: android video

我正在尝试创建一个演示文稿,它将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,在播放时没有播放任何内容。只要黑屏和播放器一开始就会停止。

我是否给出了错误的开始时间和结束时间?

请帮我解决这个问题。 感谢。

2 个答案:

答案 0 :(得分:6)

好的,看起来你好了    thisthis 代码作为基础(实际上,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);

测试视频originalclipped

我没有仔细查看您的代码,但我认为问题出在correctTimeToSyncSample方法中。

<强>限制

我应该注意,使用mp4parser可能无法将视频精确地从1s切换到10s。在测试视频中,correctTimeToSyncSample已将startTimeendTime24,37更改为21.12, 43.44。 您可以跳过此方法并始终使用给定的起始端值,但您应该准备好在输出视频中出现一些故障,因为h264和类似编解码器中特定帧的内容可能取决于previos帧/帧的内容。请看一下:original videoclipped 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处理视频文件非常容易。