Skip to content

Commit 9c8109b

Browse files
committed
don't use profile/level, fix stepTimeline(), externalize output format
1 parent de9279f commit 9c8109b

File tree

6 files changed

+75
-74
lines changed

6 files changed

+75
-74
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,23 @@
44
import android.util.Log;
55

66
import net.ypresto.androidtranscoder.engine.MediaTranscoderEngine;
7+
import net.ypresto.androidtranscoder.format.MediaFormatPresets;
78

89
import java.io.FileDescriptor;
910
import java.io.IOException;
1011
import java.util.concurrent.ExecutorService;
1112
import java.util.concurrent.Executors;
13+
import java.util.concurrent.ThreadFactory;
1214

1315
public class MediaTranscoder {
1416
private static final String TAG = "MediaTranscoder";
1517
private static volatile MediaTranscoder sMediaTranscoder;
16-
private ExecutorService mExecutor = Executors.newFixedThreadPool(1); // TODO
18+
private ExecutorService mExecutor = Executors.newFixedThreadPool(1, new ThreadFactory() {
19+
@Override
20+
public Thread newThread(Runnable r) {
21+
return new Thread(r, "MediaTranscoder-Worker");
22+
}
23+
}); // TODO
1724

1825
public interface Listener {
1926
void onTranscodeCompleted();
@@ -50,7 +57,7 @@ public void run() {
5057
try {
5158
MediaTranscoderEngine engine = new MediaTranscoderEngine();
5259
engine.setDataSource(inFileDescriptor);
53-
engine.transcode(outPath);
60+
engine.transcode(outPath, MediaFormatPresets.getExportPreset960x540());
5461
} catch (IOException e) {
5562
Log.w(TAG, "Transcode failed: input file (fd: " + inFileDescriptor.toString() + ") not found"
5663
+ " or could not open output file ('" + outPath + "') .", e);

lib/src/main/java/net/ypresto/androidtranscoder/compat/MediaCodecListCompat.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,8 @@ private String findCoderForFormat(MediaFormat format, boolean findEncoder) {
3636
while (iterator.hasNext()) {
3737
MediaCodecInfo codecInfo = iterator.next();
3838
if (codecInfo.isEncoder() != findEncoder) continue;
39-
if (!Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) continue;
40-
MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
41-
for (MediaCodecInfo.CodecProfileLevel profileLevel : capabilities.profileLevels) {
42-
if (profileLevel.profile == format.getInteger(MediaCoderExtraConstants.KEY_PROFILE)
43-
&& profileLevel.level < format.getInteger(MediaCoderExtraConstants.KEY_LEVEL)) {
44-
return codecInfo.getName();
45-
}
39+
if (Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) {
40+
return codecInfo.getName();
4641
}
4742
}
4843
return null;
Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package net.ypresto.androidtranscoder.engine;
22

3-
import android.media.MediaCodecInfo;
43
import android.media.MediaExtractor;
54
import android.media.MediaFormat;
65
import android.media.MediaMetadataRetriever;
76
import android.media.MediaMuxer;
7+
import android.util.Log;
88

99
import net.ypresto.androidtranscoder.utils.MediaExtractorUtils;
1010

@@ -13,32 +13,16 @@
1313

1414
// TODO: treat encrypted data
1515
public class MediaTranscoderEngine {
16-
private static final MediaFormat OUTPUT_VIDEO_FORMAT;
17-
private static final long SLEEP_TO_WAIT_TRACK_TRANSCODERS = 100;
16+
private static final String TAG = "MediaTranscoderEngine";
17+
private static final long SLEEP_TO_WAIT_TRACK_TRANSCODERS = 10;
1818

1919
static {
20-
// Refer: https://gist.github.com/wobbals/3990442
21-
// Refer: https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html#//apple_ref/doc/uid/TP40008332-CH102-SW8
22-
// Refer: (ANDROID ROOT)/media/libstagefright/ACodec.cpp
23-
/*
24-
OUTPUT_VIDEO_FORMAT = MediaFormat.createVideoFormat("video/avc", 640, 480); // TODO
25-
OUTPUT_VIDEO_FORMAT.setInteger(MediaFormat.KEY_BIT_RATE, 5375 * 1000); // TODO
26-
OUTPUT_VIDEO_FORMAT.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
27-
OUTPUT_VIDEO_FORMAT.setFloat(MediaFormat.KEY_FRAME_RATE, 29.97f); // NTSC, recommended by apple
28-
OUTPUT_VIDEO_FORMAT.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); // FIXME
29-
*/
30-
OUTPUT_VIDEO_FORMAT = MediaFormat.createVideoFormat("video/avc", 320, 240); // TODO
31-
OUTPUT_VIDEO_FORMAT.setInteger(MediaFormat.KEY_BIT_RATE, 2000000); // TODO
32-
OUTPUT_VIDEO_FORMAT.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
33-
OUTPUT_VIDEO_FORMAT.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // NTSC, recommended by apple
34-
OUTPUT_VIDEO_FORMAT.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); // FIXME
3520
}
3621

3722
private FileDescriptor mInputFileDescriptor;
3823
private TrackTranscoder mVideoTrackTranscoder;
3924
private TrackTranscoder mAudioTrackTranscoder;
4025
private MediaExtractor mExtractor;
41-
private MediaExtractor mExtractor2;
4226
private MediaMuxer mMuxer;
4327

4428
public MediaTranscoderEngine() {
@@ -52,7 +36,7 @@ public void setDataSource(FileDescriptor fileDescriptor) {
5236
* Run transcoding. Blocks current thread.
5337
* @throws IOException when extractor or muxer cannot open file.
5438
*/
55-
public void transcode(String outputPath) throws IOException {
39+
public void transcode(String outputPath, MediaFormat outputFormat) throws IOException {
5640
if (outputPath == null) {
5741
throw new NullPointerException("Output path cannot be null.");
5842
}
@@ -62,10 +46,8 @@ public void transcode(String outputPath) throws IOException {
6246
try {
6347
mExtractor = new MediaExtractor();
6448
mExtractor.setDataSource(mInputFileDescriptor);
65-
mExtractor2 = new MediaExtractor();
66-
mExtractor2.setDataSource(mInputFileDescriptor);
6749
mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
68-
setupTrackTranscoders();
50+
setupTrackTranscoders(outputFormat);
6951
mMuxer.start();
7052
runPipelines();
7153
mMuxer.stop();
@@ -82,10 +64,6 @@ public void transcode(String outputPath) throws IOException {
8264
mExtractor.release();
8365
mExtractor = null;
8466
}
85-
if (mExtractor2 != null) {
86-
mExtractor2.release();
87-
mExtractor2 = null;
88-
}
8967
if (mMuxer != null) {
9068
mMuxer.release();
9169
mMuxer = null;
@@ -99,31 +77,37 @@ private void setupProgressCalculation() throws IOException {
9977
// TODO
10078
}
10179

102-
private void setupTrackTranscoders() {
80+
private void setupTrackTranscoders(MediaFormat outputFormat) {
10381
MediaExtractorUtils.TrackResult trackResult = MediaExtractorUtils.getFirstVideoAndAudioTrack(mExtractor);
104-
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, OUTPUT_VIDEO_FORMAT, mMuxer);
82+
mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, outputFormat, mMuxer);
10583
mVideoTrackTranscoder.setup();
106-
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor2, trackResult.mAudioTrackIndex, mMuxer);
84+
mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, mMuxer);
10785
mAudioTrackTranscoder.setup();
10886
mVideoTrackTranscoder.determineFormat();
10987
mAudioTrackTranscoder.determineFormat();
11088
mVideoTrackTranscoder.addTrackToMuxer();
11189
mAudioTrackTranscoder.addTrackToMuxer();
11290
mExtractor.selectTrack(trackResult.mVideoTrackIndex);
113-
mExtractor2.selectTrack(trackResult.mAudioTrackIndex);
91+
mExtractor.selectTrack(trackResult.mAudioTrackIndex);
11492
}
11593

11694
private void runPipelines() {
95+
int stepCount = 0;
11796
while (!(mVideoTrackTranscoder.isFinished() && mAudioTrackTranscoder.isFinished())) {
118-
boolean stepped = mVideoTrackTranscoder.stepPipeline();
119-
stepped |= mAudioTrackTranscoder.stepPipeline();
97+
boolean stepped = mVideoTrackTranscoder.stepPipeline()
98+
|| mAudioTrackTranscoder.stepPipeline();
99+
if (true) continue;
120100
if (!stepped) {
121101
try {
102+
Log.v(TAG, "Sleeping " + SLEEP_TO_WAIT_TRACK_TRANSCODERS + "msec, " + stepCount + " steps run after last sleep.");
122103
Thread.sleep(SLEEP_TO_WAIT_TRACK_TRANSCODERS);
123104
} catch (InterruptedException e) {
124105
// nothing to do
125106
}
107+
stepCount = 0;
108+
continue;
126109
}
110+
stepCount++;
127111
}
128112
}
129113
}

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

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -99,34 +99,25 @@ public void determineFormat() {
9999
throw new IllegalStateException("Actual output format could not be determined for track: " + mTrackIndex);
100100
}
101101
} finally {
102-
// resetCodecs();
103-
// mExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); // reset position
104-
// mExtractor.unselectTrack(mTrackIndex);
102+
resetCodecs();
103+
mExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); // reset position
104+
mExtractor.unselectTrack(mTrackIndex);
105105
}
106106
}
107107

108-
// FIXME: busy implementation IS WRONG!
109108
@Override
110109
public boolean stepPipeline() {
111110
boolean busy = false;
112111

113-
/*
114-
if (!mWritingToMuxerStarted) {
115-
int extractorResult;
116-
// NOTE: keep from crash after flushing.
117-
while ((extractorResult = drainExtractor(0)) == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY) { busy = true; }
118-
if (extractorResult != DRAIN_STATE_CONSUMED) {
119-
// not ready yet.
120-
return busy;
121-
}
122-
mWritingToMuxerStarted = true;
123-
}
124-
*/
125-
112+
int status;
126113
while (drainEncoder(0, true) != DRAIN_STATE_NONE) { busy = true; }
127-
// NOTE: not repeating to keep from deadlock when encoder is full.
128-
while (drainDecoder(0) == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY) { busy = true; }
129-
while (drainExtractor(0) == DRAIN_STATE_CONSUMED) { busy = true; }
114+
do {
115+
status = drainDecoder(0);
116+
if (status != DRAIN_STATE_NONE) busy = true;
117+
// NOTE: not repeating to keep from deadlock when encoder is full.
118+
} while (status == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY);
119+
while (drainExtractor(0) != DRAIN_STATE_NONE) { busy = true; }
120+
130121
return busy;
131122
}
132123

@@ -138,6 +129,14 @@ public boolean isFinished() {
138129
// TODO: CloseGuard
139130
@Override
140131
public void release() {
132+
if (mDecoderOutputSurfaceWrapper != null) {
133+
mDecoderOutputSurfaceWrapper.release();
134+
mDecoderOutputSurfaceWrapper = null;
135+
}
136+
if (mEncoderInputSurfaceWrapper != null) {
137+
mEncoderInputSurfaceWrapper.release();
138+
mEncoderInputSurfaceWrapper = null;
139+
}
141140
if (mDecoder != null) {
142141
if (mDecoderStarted) mDecoder.stop();
143142
mDecoder.release();
@@ -148,14 +147,6 @@ public void release() {
148147
mEncoder.release();
149148
mEncoder = null;
150149
}
151-
if (mDecoderOutputSurfaceWrapper != null) {
152-
mDecoderOutputSurfaceWrapper.release();
153-
mDecoderOutputSurfaceWrapper = null;
154-
}
155-
if (mEncoderInputSurfaceWrapper != null) {
156-
mEncoderInputSurfaceWrapper.release();
157-
mEncoderInputSurfaceWrapper = null;
158-
}
159150
}
160151

161152
private void resetCodecs() {
@@ -197,6 +188,7 @@ private int drainDecoder(long timeoutUs) {
197188
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
198189
mEncoder.signalEndOfInputStream();
199190
mIsDecoderEOS = true;
191+
mBufferInfo.size = 0;
200192
}
201193
boolean doRender = (mBufferInfo.size > 0);
202194
// NOTE: doRender will block if buffer (of encoder) is full.
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
package net.ypresto.androidtranscoder.compat;
1+
package net.ypresto.androidtranscoder.format;
22

3-
public class MediaCoderExtraConstants {
4-
// from API level >= 21;
3+
public class MediaFormatExtraConstants {
4+
// from API level >= 21, but might be usable in older APIs as native code implementation exists.
55
public static final String KEY_PROFILE = "profile"; // MediaCodecInfo.CodecProfileLevel
66
// from (ANDROID ROOT)/media/libstagefright/ACodec.cpp
77
public static final String KEY_LEVEL = "level"; // MediaCodecInfo.CodecProfileLevel
8-
private MediaCoderExtraConstants() {
8+
private MediaFormatExtraConstants() {
99
}
1010
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package net.ypresto.androidtranscoder.format;
2+
3+
import android.media.MediaCodecInfo;
4+
import android.media.MediaFormat;
5+
6+
// Refer for example: https://gist.github.com/wobbals/3990442
7+
// Refer for preferred parameters: https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html#//apple_ref/doc/uid/TP40008332-CH102-SW8
8+
// Refer for available keys: (ANDROID ROOT)/media/libstagefright/ACodec.cpp
9+
public class MediaFormatPresets {
10+
11+
private MediaFormatPresets() {
12+
}
13+
14+
// preset similar to iOS SDK's AVAssetExportPreset960x540
15+
public static MediaFormat getExportPreset960x540() {
16+
MediaFormat format = MediaFormat.createVideoFormat("video/avc", 960, 540);
17+
format.setInteger(MediaFormat.KEY_BIT_RATE, 5400 * 1000);
18+
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
19+
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
20+
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
21+
return format;
22+
}
23+
}

0 commit comments

Comments
 (0)