Skip to content

Commit 5ef5974

Browse files
committed
add progress listener interface, set rotation to muxer
1 parent cbb4c5c commit 5ef5974

File tree

7 files changed

+163
-37
lines changed

7 files changed

+163
-37
lines changed

example/src/main/java/net/ypresto/androidtranscoder/example/TranscoderActivity.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import android.view.Menu;
1111
import android.view.MenuItem;
1212
import android.view.View;
13+
import android.widget.ProgressBar;
1314
import android.widget.Toast;
1415

1516
import net.ypresto.androidtranscoder.MediaTranscoder;
@@ -57,9 +58,24 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
5758
return;
5859
}
5960
final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
61+
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
62+
progressBar.setMax(1000);
6063
MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), new MediaTranscoder.Listener() {
64+
@Override
65+
public void onTranscodeProgress(double progress) {
66+
if (progress < 0) {
67+
progressBar.setIndeterminate(true);
68+
} else {
69+
progressBar.setIndeterminate(false);
70+
progressBar.setProgress((int) Math.round(progress * 1000));
71+
}
72+
}
73+
6174
@Override
6275
public void onTranscodeCompleted() {
76+
findViewById(R.id.select_video_button).setEnabled(true);
77+
progressBar.setIndeterminate(false);
78+
progressBar.setProgress(1000);
6379
startActivity(new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file), "video/mp4"));
6480
try {
6581
parcelFileDescriptor.close();
@@ -70,6 +86,9 @@ public void onTranscodeCompleted() {
7086

7187
@Override
7288
public void onTranscodeFailed(Exception exception) {
89+
progressBar.setIndeterminate(false);
90+
progressBar.setProgress(0);
91+
findViewById(R.id.select_video_button).setEnabled(true);
7392
Toast.makeText(TranscoderActivity.this, "Transcoder error occurred.", Toast.LENGTH_LONG).show();
7493
try {
7594
parcelFileDescriptor.close();
@@ -78,6 +97,7 @@ public void onTranscodeFailed(Exception exception) {
7897
}
7998
}
8099
});
100+
findViewById(R.id.select_video_button).setEnabled(false);
81101
}
82102
break;
83103
}

example/src/main/res/layout/activity_transcoder.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,12 @@
2222
android:layout_gravity="center_horizontal"
2323
android:text="Select Video File" />
2424

25+
<ProgressBar
26+
android:id="@+id/progress_bar"
27+
style="?android:attr/progressBarStyleHorizontal"
28+
android:layout_width="200dp"
29+
android:layout_height="wrap_content"
30+
android:layout_gravity="center_horizontal"
31+
android:progress="0" />
32+
2533
</LinearLayout>

lib/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ public Thread newThread(Runnable r) {
2222
}
2323
}); // TODO
2424

25-
public interface Listener {
26-
void onTranscodeCompleted();
27-
28-
void onTranscodeFailed(Exception exception);
25+
private MediaTranscoder() {
2926
}
3027

3128
public static MediaTranscoder getInstance() {
@@ -39,14 +36,12 @@ public static MediaTranscoder getInstance() {
3936
return sMediaTranscoder;
4037
}
4138

42-
private MediaTranscoder() {
43-
}
44-
4539
/**
4640
* Transcodes video file asynchronously.
41+
*
4742
* @param inFileDescriptor FileDescriptor for input.
48-
* @param outPath File path for output.
49-
* @param listener listener instance for callback.
43+
* @param outPath File path for output.
44+
* @param listener Listener instance for callback.
5045
*/
5146
public void transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final Listener listener) {
5247
final Handler handler = new Handler();
@@ -56,14 +51,25 @@ public void run() {
5651
Exception caughtException = null;
5752
try {
5853
MediaTranscoderEngine engine = new MediaTranscoderEngine();
54+
engine.setProgressCallback(new MediaTranscoderEngine.ProgressCallback() {
55+
@Override
56+
public void onProgress(final double progress) {
57+
handler.post(new Runnable() { // TODO: reuse instance
58+
@Override
59+
public void run() {
60+
listener.onTranscodeProgress(progress);
61+
}
62+
});
63+
}
64+
});
5965
engine.setDataSource(inFileDescriptor);
6066
engine.transcodeVideo(outPath, MediaFormatPresets.getExportPreset960x540());
6167
} catch (IOException e) {
6268
Log.w(TAG, "Transcode failed: input file (fd: " + inFileDescriptor.toString() + ") not found"
6369
+ " or could not open output file ('" + outPath + "') .", e);
6470
caughtException = e;
6571
} catch (RuntimeException e) {
66-
Log.e(TAG, "Fatal error while transcoding, this might be bug in engine or Android.", e);
72+
Log.e(TAG, "Fatal error while transcoding, this might be invalid output format or bug in engine or Android.", e);
6773
caughtException = e;
6874
}
6975

@@ -81,4 +87,26 @@ public void run() {
8187
}
8288
});
8389
}
90+
91+
public interface Listener {
92+
/**
93+
* Called to notify progress.
94+
*
95+
* @param progress Progress in [0.0, 1.0] range, or negative value if progress is unknown.
96+
*/
97+
void onTranscodeProgress(double progress);
98+
99+
/**
100+
* Called when transcode completed.
101+
*/
102+
void onTranscodeCompleted();
103+
104+
/**
105+
* Called when transcode failed.
106+
*
107+
* @param exception Exception caused failure. Note that it IS NOT {@link java.lang.Throwable}.
108+
* This means {@link java.lang.Error} won't be caught.
109+
*/
110+
void onTranscodeFailed(Exception exception);
111+
}
84112
}

lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,14 @@
1515
public class MediaTranscoderEngine {
1616
private static final String TAG = "MediaTranscoderEngine";
1717
private static final long SLEEP_TO_WAIT_TRACK_TRANSCODERS = 10;
18-
19-
static {
20-
}
21-
18+
private static final long PROGRESS_INTERVAL_STEPS = 10;
2219
private FileDescriptor mInputFileDescriptor;
2320
private TrackTranscoder mVideoTrackTranscoder;
2421
private TrackTranscoder mAudioTrackTranscoder;
2522
private MediaExtractor mExtractor;
2623
private MediaMuxer mMuxer;
24+
private ProgressCallback mProgressCallback;
25+
private long mDurationUs;
2726

2827
public MediaTranscoderEngine() {
2928
}
@@ -32,11 +31,19 @@ public void setDataSource(FileDescriptor fileDescriptor) {
3231
mInputFileDescriptor = fileDescriptor;
3332
}
3433

34+
public ProgressCallback getProgressCallback() {
35+
return mProgressCallback;
36+
}
37+
38+
public void setProgressCallback(ProgressCallback progressCallback) {
39+
mProgressCallback = progressCallback;
40+
}
41+
3542
/**
3643
* Run video transcoding. Blocks current thread.
3744
* Audio data will not be transcoded; original stream will be wrote to output file.
3845
*
39-
* @param outputPath File path to output transcoded video file.
46+
* @param outputPath File path to output transcoded video file.
4047
* @param videoFormat Output video format.
4148
* @throws IOException when input or output file could not be opened.
4249
*/
@@ -48,37 +55,66 @@ public void transcodeVideo(String outputPath, MediaFormat videoFormat) throws IO
4855
throw new IllegalStateException("Data source is not set.");
4956
}
5057
try {
58+
// NOTE: use single extractor to keep from running out audio track fast.
5159
mExtractor = new MediaExtractor();
5260
mExtractor.setDataSource(mInputFileDescriptor);
5361
mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
62+
setupMetadata();
5463
setupTrackTranscoders(videoFormat);
5564
mMuxer.start();
5665
runPipelines();
5766
mMuxer.stop();
5867
} finally {
59-
if (mVideoTrackTranscoder != null) {
60-
mVideoTrackTranscoder.release();
61-
mVideoTrackTranscoder = null;
62-
}
63-
if (mAudioTrackTranscoder != null) {
64-
mAudioTrackTranscoder.release();
65-
mAudioTrackTranscoder = null;
66-
}
67-
if (mExtractor != null) {
68-
mExtractor.release();
69-
mExtractor = null;
68+
try {
69+
if (mVideoTrackTranscoder != null) {
70+
mVideoTrackTranscoder.release();
71+
mVideoTrackTranscoder = null;
72+
}
73+
if (mAudioTrackTranscoder != null) {
74+
mAudioTrackTranscoder.release();
75+
mAudioTrackTranscoder = null;
76+
}
77+
if (mExtractor != null) {
78+
mExtractor.release();
79+
mExtractor = null;
80+
}
81+
} catch (RuntimeException e) {
82+
// Too fatal to make alive the app, because it may leak native resources.
83+
//noinspection ThrowFromFinallyBlock
84+
throw new Error("Could not shutdown extractor, codecs and muxer pipeline.", e);
7085
}
71-
if (mMuxer != null) {
72-
mMuxer.release();
73-
mMuxer = null;
86+
try {
87+
if (mMuxer != null) {
88+
mMuxer.release();
89+
mMuxer = null;
90+
}
91+
} catch (RuntimeException e) {
92+
Log.e(TAG, "Failed to release muxer.", e);
7493
}
7594
}
7695
}
7796

78-
private void setupProgressCalculation() throws IOException {
97+
private void setupMetadata() throws IOException {
7998
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
8099
mediaMetadataRetriever.setDataSource(mInputFileDescriptor);
81-
// TODO
100+
101+
String rotationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
102+
try {
103+
mMuxer.setOrientationHint(Integer.parseInt(rotationString));
104+
} catch (NumberFormatException e) {
105+
// skip
106+
}
107+
108+
// TODO: parse ISO 6709
109+
// String locationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
110+
// mMuxer.setLocation(Integer.getInteger(rotationString, 0));
111+
112+
try {
113+
mDurationUs = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000;
114+
} catch (NumberFormatException e) {
115+
mDurationUs = -1;
116+
}
117+
Log.d(TAG, "Duration (us): " + mDurationUs);
82118
}
83119

84120
private void setupTrackTranscoders(MediaFormat outputFormat) {
@@ -96,22 +132,35 @@ private void setupTrackTranscoders(MediaFormat outputFormat) {
96132
}
97133

98134
private void runPipelines() {
99-
int stepCount = 0;
135+
long loopCount = 0;
136+
if (mDurationUs <= 0 && mProgressCallback != null) {
137+
mProgressCallback.onProgress(-1.0); // unknown
138+
}
100139
while (!(mVideoTrackTranscoder.isFinished() && mAudioTrackTranscoder.isFinished())) {
101140
boolean stepped = mVideoTrackTranscoder.stepPipeline()
102141
|| mAudioTrackTranscoder.stepPipeline();
103-
if (true) continue;
142+
loopCount++;
143+
if (mDurationUs > 0 && mProgressCallback != null && loopCount % PROGRESS_INTERVAL_STEPS == 0) {
144+
double videoProgress = mVideoTrackTranscoder.isFinished() ? 1.0 : Math.min(1.0, (double) mVideoTrackTranscoder.getWrittenPresentationTimeUs() / mDurationUs);
145+
double audioProgress = mAudioTrackTranscoder.isFinished() ? 1.0 : Math.min(1.0, (double) mAudioTrackTranscoder.getWrittenPresentationTimeUs() / mDurationUs);
146+
mProgressCallback.onProgress((videoProgress + audioProgress) / 2.0);
147+
}
104148
if (!stepped) {
105149
try {
106-
Log.v(TAG, "Sleeping " + SLEEP_TO_WAIT_TRACK_TRANSCODERS + "msec, " + stepCount + " steps run after last sleep.");
107150
Thread.sleep(SLEEP_TO_WAIT_TRACK_TRANSCODERS);
108151
} catch (InterruptedException e) {
109152
// nothing to do
110153
}
111-
stepCount = 0;
112-
continue;
113154
}
114-
stepCount++;
115155
}
116156
}
157+
158+
public interface ProgressCallback {
159+
/**
160+
* Called to notify progress. Same thread which initiated transcode is used.
161+
*
162+
* @param progress Progress in [0.0, 1.0] range, or negative value if progress is unknown.
163+
*/
164+
void onProgress(double progress);
165+
}
117166
}

lib/src/main/java/net/ypresto/androidtranscoder/engine/PassThroughTrackTranscoder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class PassThroughTrackTranscoder implements TrackTranscoder {
1717
private ByteBuffer mBuffer;
1818
private boolean mIsEOS;
1919
private MediaFormat mActualOutputFormat;
20+
private long mWrittenPresentationTimeUs;
2021

2122
public PassThroughTrackTranscoder(MediaExtractor extractor,
2223
int trackIndex,
@@ -67,11 +68,17 @@ public boolean stepPipeline() {
6768
int flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0;
6869
mBufferInfo.set(0, sampleSize, mExtractor.getSampleTime(), flags);
6970
mMuxer.writeSampleData(mOutputTrackIndex, mBuffer, mBufferInfo);
71+
mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;
7072

7173
mExtractor.advance();
7274
return true;
7375
}
7476

77+
@Override
78+
public long getWrittenPresentationTimeUs() {
79+
return mWrittenPresentationTimeUs;
80+
}
81+
7582
@Override
7683
public boolean isFinished() {
7784
return mIsEOS;

lib/src/main/java/net/ypresto/androidtranscoder/engine/TrackTranscoder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ public interface TrackTranscoder {
3434
*/
3535
boolean stepPipeline();
3636

37+
/**
38+
* Get presentation time of last sample written to muxer.
39+
*
40+
* @return Presentation time in micro-second. Return value is undefined if finished writing.
41+
*/
42+
long getWrittenPresentationTimeUs();
43+
3744
boolean isFinished();
3845

3946
void release();

lib/src/main/java/net/ypresto/androidtranscoder/engine/VideoTrackTranscoder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class VideoTrackTranscoder implements TrackTranscoder {
3434
private boolean mIsEncoderEOS;
3535
private boolean mDecoderStarted;
3636
private boolean mEncoderStarted;
37+
private long mWrittenPresentationTimeUs;
3738

3839
public VideoTrackTranscoder(MediaExtractor extractor,
3940
int trackIndex,
@@ -121,6 +122,11 @@ public boolean stepPipeline() {
121122
return busy;
122123
}
123124

125+
@Override
126+
public long getWrittenPresentationTimeUs() {
127+
return mWrittenPresentationTimeUs;
128+
}
129+
124130
@Override
125131
public boolean isFinished() {
126132
return mIsEncoderEOS;
@@ -225,6 +231,7 @@ private int drainEncoder(long timeoutUs, boolean writeToMuxer) {
225231
}
226232
if (writeToMuxer) {
227233
mMuxer.writeSampleData(mMuxerTrackIndex, mEncoderOutputBuffers[result], mBufferInfo);
234+
mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;
228235
}
229236
mEncoder.releaseOutputBuffer(result, false);
230237
return DRAIN_STATE_CONSUMED;

0 commit comments

Comments
 (0)