Android:剪辑MP4视频

4
我正在尝试创建一个演示,它以mp4文件作为输入,并将其从00:01到00:10剪切为10秒。
我使用mp4parser来完成我的任务。
compile 'com.googlecode.mp4parser:isoparser:1.1.18'

我在我的Activity中有如下代码:
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个回答

9

好的,看起来你使用了 这个 或者 这个 代码作为基础(实际上,在mp4parser存储库中有太多不起作用的示例)。 不幸的是,这段代码已经相当过时,因为mp4parser进行了一些破坏性的更改;所以,你应该使用旧版本的mp4parser(我猜测是0.9.x),或修复代码-此线程将会有所帮助。

我写了一个小类,它使用mp4parser 1.1.18剪辑mp4文件:

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可能无法将视频从1秒准确地剪切到10秒。在测试视频中,correctTimeToSyncSamplestartTimeendTime24,37更改为21.12, 43.44。 您可以跳过此方法并始终使用给定的开始和结束值,但您应该准备好输出视频中可能会出现一些故障,因为h264和类似编解码器中特定帧的内容可能取决于上一个或多个帧的内容。请参考: 原始视频, 剪辑后视频

如果您确实需要控制开始和结束值,则应考虑使用ffmpeg。


我在以下代码处遇到了错误: Movie movie = MovieCreator.build(file);错误信息如下: Error:(32, 33) error: method build in class MovieCreator cannot be applied to given types; required: ReadableByteChannel found: FileDataSourceImpl reason: actual argument FileDataSourceImpl cannot be converted to ReadableByteChannel by method invocation conversion - Chintan Soni
你确定在使用mp4parser 1.1.18版本吗?这段代码完全兼容该版本:https://i.imgur.com/PeWaodT.png 无论如何,你可以随时使用类似这样的代码:"Movie movie = MovieCreator.build(src.getAbsolutePath());" 来创建一个电影对象。 - Mikalai Daronin
此外,这里有一个Intellij IDEA项目的示例:https://gitlab.com/lassana/mp4parser-test/ 希望它能解决不确定性。 - Mikalai Daronin
在我们的测试中,在安卓设备上,使用FFMPEG剪辑2分钟的视频需要5分钟才能剪裁到1分钟。因此性能不能与mp4parser相比。mp4parser只需要不到10秒就可以完成同样的操作。然而,我们在所有三星设备上都遇到了mp4parser的问题,即LazyList size()调用在递归几行后会冻结,并且没有进一步的断点触发。执行最终处于挂起状态。 - FranticRock

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处理视频文件非常容易。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接