简体   繁体   English

android:trim mp4视频

[英]android : trim mp4 video

I am trying to create a demo which takes a mp4 file as input and trims it to 10 seconds from 00:01 to 00:10 我正在尝试创建一个演示,它将一个mp4文件作为输入,从00:01到00:10将其修剪为10秒

I am using mp4parser for achieving my task. 我正在使用mp4parser来完成我的任务。

compile 'com.googlecode.mp4parser:isoparser:1.1.18'

I have following code in my activity: 我的活动中有以下代码:

MainActivity.java: 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 that is generated from my Log.x() : 从我的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

Problem: my output.mp4 file is resulted in 4KB and on playing it nothing is being played. 问题:我的output.mp4文件导致4KB并且播放时没有播放任何内容。 Just black screen and player stops as soon as it starts. 只要黑屏和播放器一启动就会停止。

Am I giving wrong start time and end time ? 我给错了开始时间和结束时间吗?

Please help me with this. 请帮我解决一下这个。 Thanks. 谢谢。

Okay, it looks like you taken this or this code as base (actually, there are too many non-working examples in mp4parser repository). 好吧,看起来你把这个这个代码作为基础(实际上,mp4parser存储库中有太多非工作的例子)。 Unfortunately, this code is quite obsolete because mp4parser had some breaking changes; 不幸的是,这段代码已经过时了,因为mp4parser有一些重大变化; so, you should use older version of mp4parser (I guess 0.9.x) or fix the code - this thread will be helpful. 所以,你应该使用旧版本的mp4parser(我猜0.9.x)或修复代码 - 这个线程会有所帮助。

I wrote a little class which cuts a mp4 file (using mp4parser 1.1.18): 我写了一个小类,用于剪切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];
    }
}

Usage: 用法:


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);

Test video : original , clipped . 测试视频原创剪辑

I didn't look at your code closely, but I suppose that the problem is in correctTimeToSyncSample method. 我没有仔细查看您的代码,但我认为问题出在correctTimeToSyncSample方法中。

Limitation 局限性

I should pay attention that it may be impossible to cut video exactly from 1s to 10s using mp4parser. 我应该注意,使用mp4parser可能无法将视频精确地从1s切换到10s。 In the test video, correctTimeToSyncSample changed startTime and endTime from 24,37 to 21.12, 43.44 . 在测试视频, correctTimeToSyncSample改变startTimeendTime24,3721.12, 43.44 You can skip this method and always use given start-end values, but you should be ready to some glitches in the output video, because a content of specific frame in h264 and similar codecs may depend on a content of previos frame/frames. 您可以跳过此方法并始终使用给定的起始端值,但您应该准备好在输出视频中出现一些故障,因为h264和类似编解码器中特定帧的内容可能取决于previos帧/帧的内容。 Please have a look: original video , clipped video . 请看一下: 原创视频剪辑视频

If you really need control start and stop values, you should think about ffmpeg. 如果你真的需要控制启动和停止值,你应该考虑ffmpeg。

You can play around video files in many ways using ffmpeg. 您可以使用ffmpeg以多种方式播放视频文件。

https://github.com/WritingMinds/ffmpeg-android-java https://github.com/WritingMinds/ffmpeg-android-java

just you need to prepare your required commands 只需要准备所需的命令

eg ffmpeg -i movie.mp4 -ss 00:00:03 -t 00:00:08 -async 1 cut.mp4 例如ffmpeg -i movie.mp4 -ss 00:00:03 -t 00:00:08 -async 1 cut.mp4

it is quite easy to handle video files using ffmpeg. 使用ffmpeg处理视频文件非常容易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM