Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement media time trim feature. #14

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import android.widget.Toast;

import net.ypresto.androidtranscoder.MediaTranscoder;
import net.ypresto.androidtranscoder.engine.MediaTrimTime;
import net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets;

import java.io.File;
Expand Down Expand Up @@ -104,7 +105,7 @@ public void onTranscodeFailed(Exception exception) {
};
Log.d(TAG, "transcoding into " + file);
mFuture = MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(),
MediaFormatStrategyPresets.createAndroid720pStrategy(), listener);
MediaFormatStrategyPresets.createAndroid720pStrategy(), new MediaTrimTime(5 * 1000000, 10 * 1000000), listener);
switchButtonEnabled(true);
}
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import android.util.Log;

import net.ypresto.androidtranscoder.engine.MediaTranscoderEngine;
import net.ypresto.androidtranscoder.engine.MediaTrimTime;
import net.ypresto.androidtranscoder.format.MediaFormatPresets;
import net.ypresto.androidtranscoder.format.MediaFormatStrategy;

Expand Down Expand Up @@ -85,7 +86,7 @@ public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) {
public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) {
return null;
}
}, listener);
}, null, listener);
}

/**
Expand Down Expand Up @@ -115,7 +116,7 @@ public Future<Void> transcodeVideo(final String inPath, final String outPath, fi
throw e;
}
final FileInputStream finalFileInputStream = fileInputStream;
return transcodeVideo(inFileDescriptor, outPath, outFormatStrategy, new Listener() {
return transcodeVideo(inFileDescriptor, outPath, outFormatStrategy, null, new Listener() {
@Override
public void onTranscodeProgress(double progress) {
listener.onTranscodeProgress(progress);
Expand Down Expand Up @@ -156,9 +157,10 @@ private void closeStream() {
* @param inFileDescriptor FileDescriptor for input.
* @param outPath File path for output.
* @param outFormatStrategy Strategy for output video format.
* @param mediaTrimTime Media trim time.
* @param listener Listener instance for callback.
*/
public Future<Void> transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final MediaFormatStrategy outFormatStrategy, final Listener listener) {
public Future<Void> transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final MediaFormatStrategy outFormatStrategy, final MediaTrimTime mediaTrimTime, final Listener listener) {
Looper looper = Looper.myLooper();
if (looper == null) looper = Looper.getMainLooper();
final Handler handler = new Handler(looper);
Expand All @@ -181,7 +183,7 @@ public void run() {
}
});
engine.setDataSource(inFileDescriptor);
engine.transcodeVideo(outPath, outFormatStrategy);
engine.transcodeVideo(outPath, outFormatStrategy, mediaTrimTime);
} catch (IOException e) {
Log.w(TAG, "Transcode failed: input file (fd: " + inFileDescriptor.toString() + ") not found"
+ " or could not open output file ('" + outPath + "') .", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class MediaTranscoderEngine {
private volatile double mProgress;
private ProgressCallback mProgressCallback;
private long mDurationUs;
private MediaTrimTime mMediaTrimTime;

/**
* Do not use this constructor unless you know what you are doing.
Expand Down Expand Up @@ -81,6 +82,21 @@ public double getProgress() {
* @throws InterruptedException when cancel to transcode.
*/
public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy) throws IOException, InterruptedException {
transcodeVideo(outputPath, formatStrategy, null);
}

/**
* Run video transcoding. Blocks current thread.
* Audio data will not be transcoded; original stream will be wrote to output file.
*
* @param outputPath File path to output transcoded video file.
* @param formatStrategy Output format strategy.
* @param mediaTrimTime Media trim time.
* @throws IOException when input or output file could not be opened.
* @throws InvalidOutputFormatException when output format is not supported.
* @throws InterruptedException when cancel to transcode.
*/
public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy, MediaTrimTime mediaTrimTime) throws IOException, InterruptedException {
if (outputPath == null) {
throw new NullPointerException("Output path cannot be null.");
}
Expand All @@ -92,6 +108,7 @@ public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy
mExtractor = new MediaExtractor();
mExtractor.setDataSource(mInputFileDescriptor);
mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
mMediaTrimTime = mediaTrimTime;
setupMetadata();
setupTrackTranscoders(formatStrategy);
runPipelines();
Expand Down Expand Up @@ -146,6 +163,14 @@ private void setupMetadata() throws IOException {
} catch (NumberFormatException e) {
mDurationUs = -1;
}

if (mMediaTrimTime != null && mMediaTrimTime.endTimeInUs > 0 && mMediaTrimTime.endTimeInUs < mDurationUs) {
mDurationUs = mMediaTrimTime.endTimeInUs;
}

if (mMediaTrimTime != null && mMediaTrimTime.startTimeInUs > 0 && mMediaTrimTime.startTimeInUs < mDurationUs ) {
mDurationUs -= mMediaTrimTime.startTimeInUs;
}
Log.d(TAG, "Duration (us): " + mDurationUs);
}

Expand All @@ -165,19 +190,23 @@ public void onDetermineOutputFormat() {
});

if (videoOutputFormat == null) {
mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, queuedMuxer, QueuedMuxer.SampleType.VIDEO);
mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, queuedMuxer, QueuedMuxer.SampleType.VIDEO, mMediaTrimTime);
} else {
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, queuedMuxer);
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, queuedMuxer, mMediaTrimTime);
}
mVideoTrackTranscoder.setup();
if (audioOutputFormat == null) {
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, queuedMuxer, QueuedMuxer.SampleType.AUDIO);
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, queuedMuxer, QueuedMuxer.SampleType.AUDIO, mMediaTrimTime);
} else {
throw new UnsupportedOperationException("Transcoding audio tracks currently not supported.");
}
mAudioTrackTranscoder.setup();
mExtractor.selectTrack(trackResult.mVideoTrackIndex);
mExtractor.selectTrack(trackResult.mAudioTrackIndex);

if (mMediaTrimTime != null && mMediaTrimTime.startTimeInUs > 0) {
mExtractor.seekTo(mMediaTrimTime.startTimeInUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
}
}

private void runPipelines() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.ypresto.androidtranscoder.engine;

/**
* Created by Taishan Lin on 1/28/16.
*/
public class MediaTrimTime {
public static final long DO_NOT_TRIM_TIME = -1;
public final long startTimeInUs;
public final long endTimeInUs;

public MediaTrimTime() {
this.startTimeInUs = DO_NOT_TRIM_TIME;
this.endTimeInUs = DO_NOT_TRIM_TIME;
}

public MediaTrimTime(long startTimeInUs, long endTimeInUs) {
this.startTimeInUs = startTimeInUs;
this.endTimeInUs = endTimeInUs;
if (endTimeInUs != DO_NOT_TRIM_TIME && startTimeInUs >= endTimeInUs) {
throw new IllegalArgumentException("Start time is larger than or equal to end time!");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ public class PassThroughTrackTranscoder implements TrackTranscoder {
private final QueuedMuxer mMuxer;
private final QueuedMuxer.SampleType mSampleType;
private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
private final MediaTrimTime mMediaTrimTime;
private int mBufferSize;
private ByteBuffer mBuffer;
private boolean mIsEOS;
private boolean mIsTrimEOS;
private MediaFormat mActualOutputFormat;
private long mWrittenPresentationTimeUs;

public PassThroughTrackTranscoder(MediaExtractor extractor, int trackIndex,
QueuedMuxer muxer, QueuedMuxer.SampleType sampleType) {
QueuedMuxer muxer, QueuedMuxer.SampleType sampleType, MediaTrimTime mediaTrimTime) {
mExtractor = extractor;
mTrackIndex = trackIndex;
mMuxer = muxer;
Expand All @@ -46,6 +48,7 @@ public PassThroughTrackTranscoder(MediaExtractor extractor, int trackIndex,
mMuxer.setOutputFormat(mSampleType, mActualOutputFormat);
mBufferSize = mActualOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
mBuffer = ByteBuffer.allocateDirect(mBufferSize).order(ByteOrder.nativeOrder());
mMediaTrimTime = mediaTrimTime;
}

@Override
Expand All @@ -71,9 +74,21 @@ public boolean stepPipeline() {
}
if (trackIndex != mTrackIndex) return false;

if (mIsTrimEOS) {
mExtractor.advance();
return true;
}
mBuffer.clear();
int sampleSize = mExtractor.readSampleData(mBuffer, 0);
assert sampleSize <= mBufferSize;
if (isPastTrimEndTime(mExtractor.getSampleTime())) {
mBuffer.clear();
mBufferInfo.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo);
mExtractor.advance();
mIsTrimEOS = true;
return true;
}
boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
int flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0;
mBufferInfo.set(0, sampleSize, mExtractor.getSampleTime(), flags);
Expand All @@ -91,7 +106,11 @@ public long getWrittenPresentationTimeUs() {

@Override
public boolean isFinished() {
return mIsEOS;
return mIsEOS || mIsTrimEOS;
}

private boolean isPastTrimEndTime(long sampleTime) {
return mMediaTrimTime != null && mMediaTrimTime.endTimeInUs > 0 && sampleTime > mMediaTrimTime.endTimeInUs;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class VideoTrackTranscoder implements TrackTranscoder {
private static final int DRAIN_STATE_CONSUMED = 2;

private final MediaExtractor mExtractor;
private final MediaTrimTime mMediaTrimTime;
private final int mTrackIndex;
private final MediaFormat mOutputFormat;
private final QueuedMuxer mMuxer;
Expand All @@ -44,18 +45,20 @@ public class VideoTrackTranscoder implements TrackTranscoder {
private OutputSurface mDecoderOutputSurfaceWrapper;
private InputSurface mEncoderInputSurfaceWrapper;
private boolean mIsExtractorEOS;
private boolean mIsTrimEOS;
private boolean mIsDecoderEOS;
private boolean mIsEncoderEOS;
private boolean mDecoderStarted;
private boolean mEncoderStarted;
private long mWrittenPresentationTimeUs;

public VideoTrackTranscoder(MediaExtractor extractor, int trackIndex,
MediaFormat outputFormat, QueuedMuxer muxer) {
MediaFormat outputFormat, QueuedMuxer muxer, MediaTrimTime mediaTrimTime) {
mExtractor = extractor;
mTrackIndex = trackIndex;
mOutputFormat = outputFormat;
mMuxer = muxer;
mMediaTrimTime = mediaTrimTime;
}

@Override
Expand Down Expand Up @@ -120,7 +123,7 @@ public long getWrittenPresentationTimeUs() {

@Override
public boolean isFinished() {
return mIsEncoderEOS;
return mIsEncoderEOS || mIsTrimEOS;
}

// TODO: CloseGuard
Expand Down Expand Up @@ -159,7 +162,17 @@ private int drainExtractor(long timeoutUs) {
mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
return DRAIN_STATE_NONE;
}
if (mIsTrimEOS) {
mExtractor.advance();
return DRAIN_STATE_NONE;
}
int sampleSize = mExtractor.readSampleData(mDecoderInputBuffers[result], 0);
if (isPastTrimEndTime(mExtractor.getSampleTime())){
mIsTrimEOS = true;
mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
mExtractor.advance();
return DRAIN_STATE_NONE;
}
boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
mDecoder.queueInputBuffer(result, 0, sampleSize, mExtractor.getSampleTime(), isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0);
mExtractor.advance();
Expand Down Expand Up @@ -228,4 +241,8 @@ private int drainEncoder(long timeoutUs) {
mEncoder.releaseOutputBuffer(result, false);
return DRAIN_STATE_CONSUMED;
}

private boolean isPastTrimEndTime(long sampleTime) {
return mMediaTrimTime != null && mMediaTrimTime.endTimeInUs > 0 && sampleTime > mMediaTrimTime.endTimeInUs;
}
}