5

I'm trying to record the screen with MediaProjection API. I want to trim the video that was recorded by the media projection. Is there a way to do that without using any 3rd party dependency?

mnagy
  • 1,085
  • 1
  • 17
  • 36

2 Answers2

15

After lots of digging, I found this snippet

  /**
 * @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
 */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
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<>(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);
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    try {
        muxer.start();
        while (true) {
            bufferInfo.offset = offset;
            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
            if (bufferInfo.size < 0) {
                InstabugSDKLogger.d(TAG, "Saw input EOS.");
                bufferInfo.size = 0;
                break;
            } else {
                bufferInfo.presentationTimeUs = extractor.getSampleTime();
                if (endMs > 0 && bufferInfo.presentationTimeUs > (endMs * 1000)) {
                    InstabugSDKLogger.d(TAG, "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();

        //deleting the old file
        File file = new File(srcPath);
        file.delete();
    } catch (IllegalStateException e) {
        // Swallow the exception due to malformed source.
        InstabugSDKLogger.w(TAG, "The source video file is malformed");
    } finally {
        muxer.release();
    }
    return;
}

EDIT: just to name the source for this. It's from the gallery app of Google, which allows to trim videos, in a file called "VideoUtils": https://android.googlesource.com/platform/packages/apps/Gallery2/+/634248d/src/com/android/gallery3d/app/VideoUtils.java

The missing code is:

private static final String LOGTAG = "VideoUtils";
private static final int DEFAULT_BUFFER_SIZE = 1 * 1024 * 1024;
android developer
  • 114,585
  • 152
  • 739
  • 1,270
mnagy
  • 1,085
  • 1
  • 17
  • 36
  • 1
    Based on this snippet, it only trim to the key frame, isn't it? Can we feed the muxer any sample data after the key frame so it can start from any B frame? – vxh.viet Nov 29 '17 at 03:49
  • 1
    Any way to do it using Uri or InputStream instead of file path? It seems you might need API 26 for this, right? I've asked about it here: stackoverflow.com/q/54503331/878126 – android developer Feb 03 '19 at 14:28
  • tried your snippet. But the video created is corrupt in some way. Says 'can't play the video file" when I am trying to play the output – dexter2019 Nov 17 '19 at 20:58
  • @mnagy trimming video with above code works perfectly. Bt for some video throws MPEG4Writer: Track (text/3gpp-tt) other than video/audio/metadata is not supported MPEG4Writer: Unsupported mime 'text/3gpp-tt'. – Mano Sep 01 '20 at 04:50
0

I am trimming with above code. The video file has 3 trackformats

  1. {track-id=1, file-format=video/mp4, level=1024, mime=video/avc, frame-count=5427, profile=8, language=, display-width=810, csd-1=java.nio.HeapByteBuffer[pos=0 lim=8 cap=8], durationUs=181080900, display-height=1440, width=810, rotation-degrees=0, max-input-size=874801, frame-rate=30, height=1440, csd-0=java.nio.HeapByteBuffer[pos=0 lim=33 cap=33]}

  2. {max-bitrate=125588, sample-rate=44100, track-id=2, file-format=video/mp4, mime=audio/mp4a-latm, profile=2, bitrate=125588, language=, aac-profile=2, durationUs=181138866, aac-format-adif=0, channel-count=2, max-input-size=65538, csd-0=java.nio.HeapByteBuffer[pos=0 lim=2 cap=2]}

  3. {text-format-data=java.nio.HeapByteBuffer[pos=0 lim=56 cap=56], track-id=3, file-format=video/mp4, durationUs=179640000, mime=text/3gpp-tt, language=, max-input-size=65536}

So I can,t trim that video. It throws MPEG4Writer: Track (text/3gpp-tt) other than video/audio/metadata is not supported MPEG4Writer: Unsupported mime 'text/3gpp-tt'

But i am only trimming video file. Kindly anyone resolve my problem

Mano
  • 79
  • 5