1515public 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}
0 commit comments