Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 48afb1f

Browse files
committed
Restrict output format, abort if no transcoding necessary
Fixes ypresto#6
1 parent 4a081c2 commit 48afb1f

File tree

10 files changed

+174
-11
lines changed

10 files changed

+174
-11
lines changed

‎lib/build.gradle‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ publish {
4040
}
4141

4242
dependencies {
43+
compile 'org.jcodec:jcodec:0.1.9'
4344
compile fileTree(dir: 'libs', include: ['*.jar'])
4445
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public static MediaTranscoder getInstance() {
6868
* @param inFileDescriptor FileDescriptor for input.
6969
* @param outPath File path for output.
7070
* @param listener Listener instance for callback.
71-
* @deprecated Use {@link #transcodeVideo(java.io.FileDescriptor, String, net.ypresto.androidtranscoder.format.MediaFormatStrategy, net.ypresto.androidtranscoder.MediaTranscoder.Listener)} which accepts output video format.
71+
* @deprecated Use {@link #transcodeVideo(FileDescriptor, String, MediaFormatStrategy, MediaTranscoder.Listener)} which accepts output video format.
7272
*/
7373
@Deprecated
7474
public void transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final Listener listener) {
@@ -177,7 +177,7 @@ public void run() {
177177
+ " or could not open output file ('" + outPath + "') .", e);
178178
caughtException = e;
179179
} catch (RuntimeException e) {
180-
Log.e(TAG, "Fatal error while transcoding, this might be invalid output format or bug in engine or Android.", e);
180+
Log.e(TAG, "Fatal error while transcoding, this might be invalid format or bug in engine or Android.", e);
181181
caughtException = e;
182182
}
183183

@@ -212,8 +212,8 @@ public interface Listener {
212212
/**
213213
* Called when transcode failed.
214214
*
215-
* @param exception Exception caused failure. Note that it IS NOT {@link java.lang.Throwable}.
216-
* This means {@link java.lang.Error} won't be caught.
215+
* @param exception Exception thrown from {@link MediaTranscoderEngine#transcodeVideo(String, MediaFormatStrategy)}.
216+
* Note that it IS NOT {@link java.lang.Throwable}. This means {@link java.lang.Error} won't be caught.
217217
*/
218218
void onTranscodeFailed(Exception exception);
219219
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (C) 2015 Yuya Tanaka
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.ypresto.androidtranscoder.engine;
17+
18+
public class InvalidOutputFormatException extends RuntimeException {
19+
public InvalidOutputFormatException(String detailMessage) {
20+
super(detailMessage);
21+
}
22+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (C) 2015 Yuya Tanaka
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.ypresto.androidtranscoder.engine;
17+
18+
import android.media.MediaFormat;
19+
20+
import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants;
21+
import net.ypresto.androidtranscoder.utils.AvcCsdUtils;
22+
23+
import org.jcodec.codecs.h264.H264Utils;
24+
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
25+
26+
import java.nio.ByteBuffer;
27+
28+
class MediaFormatValidator {
29+
// Refer: http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC#Profiles
30+
private static final int PROFILE_IDC_BASELINE = 66;
31+
32+
public static void validateVideoOutputFormat(MediaFormat format) {
33+
String mime = format.getString(MediaFormat.KEY_MIME);
34+
// Refer: http://developer.android.com/guide/appendix/media-formats.html#core
35+
// Refer: http://en.wikipedia.org/wiki/MPEG-4_Part_14#Data_streams
36+
if (!MediaFormatExtraConstants.MIMETYPE_VIDEO_AVC.equals(mime)) {
37+
throw new InvalidOutputFormatException("Video codecs other than AVC is not supported, actual mime type: " + mime);
38+
}
39+
ByteBuffer spsBuffer = AvcCsdUtils.getSpsBuffer(format);
40+
SeqParameterSet sps = H264Utils.readSPS(spsBuffer);
41+
if (sps.profile_idc != PROFILE_IDC_BASELINE) {
42+
throw new InvalidOutputFormatException("Non-baseline AVC video profile is not supported by Android OS, actual profile_idc: " + sps.profile_idc);
43+
}
44+
}
45+
46+
public static void validateAudioOutputFormat(MediaFormat format) {
47+
String mime = format.getString(MediaFormat.KEY_MIME);
48+
if (!MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC.equals(mime)) {
49+
throw new InvalidOutputFormatException("Audio codecs other than AAC is not supported, actual mime type: " + mime);
50+
}
51+
}
52+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public double getProgress() {
7777
* @param outputPath File path to output transcoded video file.
7878
* @param formatStrategy Output format strategy.
7979
* @throws IOException when input or output file could not be opened.
80+
* @throws InvalidOutputFormatException when output format is not supported.
8081
*/
8182
public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy) throws IOException {
8283
if (outputPath == null) {
@@ -152,6 +153,10 @@ private void setupTrackTranscoders(MediaFormatStrategy formatStrategy) {
152153
MediaExtractorUtils.TrackResult trackResult = MediaExtractorUtils.getFirstVideoAndAudioTrack(mExtractor);
153154
MediaFormat videoOutputFormat = formatStrategy.createVideoOutputFormat(trackResult.mVideoTrackFormat);
154155
MediaFormat audioOutputFormat = formatStrategy.createAudioOutputFormat(trackResult.mAudioTrackFormat);
156+
if (videoOutputFormat == null && audioOutputFormat == null) {
157+
throw new InvalidOutputFormatException("MediaFormatStrategy returned pass-through for both video and audio. No transcoding is necessary.");
158+
}
159+
155160
if (videoOutputFormat == null) {
156161
mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, mMuxer);
157162
} else {
@@ -166,6 +171,8 @@ private void setupTrackTranscoders(MediaFormatStrategy formatStrategy) {
166171
mAudioTrackTranscoder.setup();
167172
mVideoTrackTranscoder.determineFormat();
168173
mAudioTrackTranscoder.determineFormat();
174+
MediaFormatValidator.validateVideoOutputFormat(mVideoTrackTranscoder.getDeterminedFormat());
175+
MediaFormatValidator.validateAudioOutputFormat(mAudioTrackTranscoder.getDeterminedFormat());
169176
mVideoTrackTranscoder.addTrackToMuxer();
170177
mAudioTrackTranscoder.addTrackToMuxer();
171178
mExtractor.selectTrack(trackResult.mVideoTrackIndex);

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import android.media.MediaMuxer;
2222
import android.util.Log;
2323

24+
import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants;
25+
2426
import java.io.IOException;
2527
import java.nio.ByteBuffer;
2628

@@ -77,11 +79,11 @@ public void setup() {
7779
mEncoderOutputBuffers = mEncoder.getOutputBuffers();
7880

7981
MediaFormat inputFormat = mExtractor.getTrackFormat(mTrackIndex);
80-
if (inputFormat.containsKey("rotation-degrees")) {
82+
if (inputFormat.containsKey(MediaFormatExtraConstants.KEY_ROTATION_DEGREES)) {
8183
// Decoded video is rotated automatically in Android 5.0 lollipop.
8284
// Turn off here because we don't want to encode rotated one.
8385
// refer: https://android.googlesource.com/platform/frameworks/av/+blame/lollipop-release/media/libstagefright/Utils.cpp
84-
inputFormat.setInteger("rotation-degrees", 0);
86+
inputFormat.setInteger(MediaFormatExtraConstants.KEY_ROTATION_DEGREES, 0);
8587
}
8688
mDecoderOutputSurfaceWrapper = new OutputSurface();
8789
try {

‎lib/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import android.util.Log;
2121

2222
class Android720pFormatStrategy implements MediaFormatStrategy {
23-
private static final String TAG = "Android720pFormatStrategy";
23+
private static final String TAG = "720pFormatStrategy";
2424
private static final int LONGER_LENGTH = 1280;
2525
private static final int SHORTER_LENGTH = 720;
2626
private static final int DEFAULT_BITRATE = 8000 * 1000; // From Nexus 4 Camera in 720p

‎lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,41 @@
1616
package net.ypresto.androidtranscoder.format;
1717

1818
public class MediaFormatExtraConstants {
19-
// from API level >= 21, but might be usable in older APIs as native code implementation exists.
20-
public static final String KEY_PROFILE = "profile"; // MediaCodecInfo.CodecProfileLevel
21-
// from (ANDROID ROOT)/media/libstagefright/ACodec.cpp
22-
public static final String KEY_LEVEL = "level"; // MediaCodecInfo.CodecProfileLevel
19+
// from MediaFormat of API level >= 21, but might be usable in older APIs as native code implementation exists.
20+
// https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2621
21+
// NOTE: native code enforces baseline profile.
22+
// https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2638
23+
/** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCProfile* . */
24+
public static final String KEY_PROFILE = "profile";
25+
26+
// from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2623
27+
/** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCLevel* . */
28+
public static final String KEY_LEVEL = "level";
29+
30+
// from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/MediaCodec.cpp#2197
31+
/** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */
32+
public static final String KEY_AVC_SPS = "csd-0";
33+
/** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */
34+
public static final String KEY_AVC_PPS = "csd-1";
35+
36+
/**
37+
* For decoder parameter and included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}.
38+
* Decoder rotates specified degrees before rendering video to surface.
39+
* NOTE: Only included in track format of API >= 21.
40+
*/
41+
public static final String KEY_ROTATION_DEGREES = "rotation-degrees";
42+
43+
// Video formats
44+
// from MediaFormat of API level >= 21
45+
public static final String MIMETYPE_VIDEO_AVC = "video/avc";
46+
public static final String MIMETYPE_VIDEO_H263 = "video/3gpp";
47+
public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
48+
49+
// Audio formats
50+
// from MediaFormat of API level >= 21
51+
public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
52+
2353
private MediaFormatExtraConstants() {
54+
throw new RuntimeException();
2455
}
2556
}

‎lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class MediaFormatStrategyPresets {
2525
/**
2626
* Preset based on Nexus 4 camera recording with 720p quality.
2727
* This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available).
28+
* Default bitrate is 8Mbps. {@link #createAndroid720pStrategy(int)} to specify bitrate.
2829
*/
2930
public static MediaFormatStrategy createAndroid720pStrategy() {
3031
return new Android720pFormatStrategy();
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (C) 2015 Yuya Tanaka
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.ypresto.androidtranscoder.utils;
17+
18+
import android.media.MediaFormat;
19+
20+
import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants;
21+
22+
import java.nio.ByteBuffer;
23+
import java.util.Arrays;
24+
25+
public class AvcCsdUtils {
26+
// Refer: https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/MediaCodec.cpp#2198
27+
private static final byte[] AVC_CSD_PREFIX = {0x00, 0x00, 0x00, 0x01};
28+
// Refer: http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/
29+
private static final byte AVC_SPS_NAL = 103; // 0<<7 + 3<<5 + 7<<0
30+
31+
public static ByteBuffer getSpsBuffer(MediaFormat format) {
32+
ByteBuffer prefixedSpsBuffer = format.getByteBuffer(MediaFormatExtraConstants.KEY_AVC_SPS).asReadOnlyBuffer();
33+
byte[] csdPrefix = new byte[4];
34+
prefixedSpsBuffer.get(csdPrefix);
35+
if (!Arrays.equals(csdPrefix, AVC_CSD_PREFIX)) {
36+
throw new IllegalStateException("Wrong csd-0 prefix.");
37+
}
38+
if (prefixedSpsBuffer.get() != AVC_SPS_NAL) {
39+
throw new IllegalStateException("Got non SPS NAL data.");
40+
}
41+
return prefixedSpsBuffer.slice();
42+
}
43+
44+
private AvcCsdUtils() {
45+
throw new RuntimeException();
46+
}
47+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /