Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3eb2fb4d authored by ztenghui's avatar ztenghui Committed by Android (Google) Code Review
Browse files

Merge "Use MediaMuxer for the video trim and mute." into gb-ub-photos-bryce

parents 2fc6641b 8b9de91b
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ public class ApiHelper {
        public static final int ICE_CREAM_SANDWICH_MR1 = 15;
        public static final int JELLY_BEAN = 16;
        public static final int JELLY_BEAN_MR1 = 17;
        public static final int JELLY_BEAN_MR2 = 18;
    }

    public static final boolean AT_LEAST_16 = Build.VERSION.SDK_INT >= 16;
@@ -188,6 +189,9 @@ public class ApiHelper {
    public static final boolean HAS_CANCELLATION_SIGNAL =
            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;

    public static final boolean HAS_MEDIA_MUXER =
                    Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2;

    public static int getIntFieldIfExists(Class<?> klass, String fieldName,
            Class<?> obj, int defaultVal) {
        try {
+1 −1
Original line number Diff line number Diff line
@@ -237,7 +237,7 @@ public class TrimVideo extends Activity implements
            public void run() {
                try {
                    VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile,
                            mTrimStartTime, mTrimEndTime, mVideoView.getDuration());
                            mTrimStartTime, mTrimEndTime);
                    // Update the database for adding a new video file.
                    SaveVideoFileUtils.insertContent(mDstFileInfo,
                            getContentResolver(), mUri);
+178 −30
Original line number Diff line number Diff line
@@ -19,6 +19,14 @@

package com.android.gallery3d.app;

import android.media.MediaCodec.BufferInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.util.Log;

import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.util.SaveVideoFileInfo;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.boxes.TimeToSampleBox;
@@ -29,17 +37,49 @@ import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

public class VideoUtils {
    private static final String LOGTAG = "VideoUtils";
    private static final int DEFAULT_BUFFER_SIZE = 1 * 1024 * 1024;

    /**
     * Remove the sound track.
     */
    public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo)
            throws IOException {
        if (ApiHelper.HAS_MEDIA_MUXER) {
            genVideoUsingMuxer(filePath, dstFileInfo.mFile.getPath(), -1, -1,
                    false, true);
        } else {
            startMuteUsingMp4Parser(filePath, dstFileInfo);
        }
    }

    /**
     * Shortens/Crops tracks
     */
    public static void startTrim(File src, File dst, int startMs, int endMs)
            throws IOException {
        if (ApiHelper.HAS_MEDIA_MUXER) {
            genVideoUsingMuxer(src.getPath(), dst.getPath(), startMs, endMs,
                    true, true);
        } else {
            trimUsingMp4Parser(src, dst, startMs, endMs);
        }
    }

    public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo) throws IOException {
    private static void startMuteUsingMp4Parser(String filePath,
            SaveVideoFileInfo dstFileInfo) throws FileNotFoundException, IOException {
        File dst = dstFileInfo.mFile;
        File src = new File(filePath);
        RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
@@ -74,9 +114,116 @@ public class VideoUtils {
    }

    /**
     * Shortens/Crops a track
     * @param srcPath the path of source video file.
     * @param dstPath the path of destination video file.
     * @param startMs starting time in milliseconds for trimming. Set to
     *            negative if starting from beginning.
     * @param endMs end time for trimming in milliseconds. Set to negative if
     *            no trimming at the end.
     * @param useAudio true if keep the audio track from the source.
     * @param useVideo true if keep the video track from the source.
     * @throws IOException
     */
    public static void startTrim(File src, File dst, int startMs, int endMs, int totalMs) throws IOException {
    private static void genVideoUsingMuxer(String srcPath, String dstPath,
            int startMs, int endMs, boolean useAudio, boolean useVideo)
            throws IOException {
        // Set up MediaExtractor to read from the source.
        MediaExtractor extractor = new MediaExtractor();
        extractor.setDataSource(srcPath);

        int trackCount = extractor.getTrackCount();

        // Set up MediaMuxer for the destination.
        MediaMuxer muxer;
        muxer = new MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

        // Set up the tracks and retrieve the max buffer size for selected
        // tracks.
        HashMap<Integer, Integer> indexMap = new HashMap<Integer,
                Integer>(trackCount);
        int bufferSize = -1;
        for (int i = 0; i < trackCount; i++) {
            MediaFormat format = extractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);

            boolean selectCurrentTrack = false;

            if (mime.startsWith("audio/") && useAudio) {
                selectCurrentTrack = true;
            } else if (mime.startsWith("video/") && useVideo) {
                selectCurrentTrack = true;
            }

            if (selectCurrentTrack) {
                extractor.selectTrack(i);
                int dstIndex = muxer.addTrack(format);
                indexMap.put(i, dstIndex);
                if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
                    int newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
                    bufferSize = newSize > bufferSize ? newSize : bufferSize;
                }
            }
        }

        if (bufferSize < 0) {
            bufferSize = DEFAULT_BUFFER_SIZE;
        }

        // Set up the orientation and starting time for extractor.
        MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
        retrieverSrc.setDataSource(srcPath);
        String degreesString = retrieverSrc.extractMetadata(
                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
        if (degreesString != null) {
            int degrees = Integer.parseInt(degreesString);
            if (degrees >= 0) {
                muxer.setOrientationHint(degrees);
            }
        }

        if (startMs > 0) {
            extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
        }

        // Copy the samples from MediaExtractor to MediaMuxer. We will loop
        // for copying each sample and stop when we get to the end of the source
        // file or exceed the end time of the trimming.
        int offset = 0;
        int trackIndex = -1;
        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
        BufferInfo bufferInfo = new BufferInfo();

        muxer.start();
        while (true) {
            bufferInfo.offset = offset;
            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
            if (bufferInfo.size < 0) {
                Log.d(LOGTAG, "Saw input EOS.");
                bufferInfo.size = 0;
                break;
            } else {
                bufferInfo.presentationTimeUs = extractor.getSampleTime();
                if (endMs > 0 && bufferInfo.presentationTimeUs > (endMs * 1000)) {
                    Log.d(LOGTAG, "The current sample is over the trim end time.");
                    break;
                } else {
                    bufferInfo.flags = extractor.getSampleFlags();
                    trackIndex = extractor.getSampleTrackIndex();

                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
                            bufferInfo);
                    extractor.advance();
                }
            }
        }

        muxer.stop();
        muxer.release();
        return;
    }

    private static void trimUsingMp4Parser(File src, File dst, int startMs, int endMs)
            throws FileNotFoundException, IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
        Movie movie = MovieCreator.build(randomAccessFile.getChannel());

@@ -89,17 +236,20 @@ public class VideoUtils {

        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
        // 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.");
                    // 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);
@@ -116,20 +266,23 @@ public class VideoUtils {
            for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
                TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
                for (int j = 0; j < entry.getCount(); j++) {
                    // entry.getDelta() is the amount of time the current sample covers.
                    // entry.getDelta() is the amount of time the current sample
                    // covers.

                    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
                        // 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) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
                    currentTime += (double) entry.getDelta()
                            / (double) track.getTrackMetaData().getTimescale();
                    currentSample++;
                }
            }
@@ -139,15 +292,8 @@ public class VideoUtils {
        randomAccessFile.close();
    }

    protected static long getDuration(Track track) {
        long duration = 0;
        for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
            duration += entry.getCount() * entry.getDelta();
        }
        return duration;
    }

    private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
    private static double correctTimeToSyncSample(Track track, double cutHere,
            boolean next) {
        double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
        long currentSample = 0;
        double currentTime = 0;
@@ -155,10 +301,13 @@ public class VideoUtils {
            TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().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;
                    // samples always start with 1 but we start with zero
                    // therefore +1
                    timeOfSyncSamples[Arrays.binarySearch(
                            track.getSyncSamples(), currentSample + 1)] = currentTime;
                }
                currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
                currentTime += (double) entry.getDelta()
                        / (double) track.getTrackMetaData().getTimescale();
                currentSample++;
            }
        }
@@ -176,5 +325,4 @@ public class VideoUtils {
        return timeOfSyncSamples[timeOfSyncSamples.length - 1];
    }


}