diff options
Diffstat (limited to 'libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format')
8 files changed, 492 insertions, 0 deletions
diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android16By9FormatStrategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android16By9FormatStrategy.java new file mode 100644 index 000000000..faf303127 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android16By9FormatStrategy.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaCodecInfo; +import android.media.MediaFormat; +import android.util.Log; + +class Android16By9FormatStrategy implements MediaFormatStrategy { + public static final int AUDIO_BITRATE_AS_IS = -1; + public static final int AUDIO_CHANNELS_AS_IS = -1; + public static final int SCALE_720P = 5; + private static final String TAG = "Android16By9FormatStrategy"; + private final int mScale; + private final int mVideoBitrate; + private final int mAudioBitrate; + private final int mAudioChannels; + + public Android16By9FormatStrategy(int scale, int videoBitrate) { + this(scale, videoBitrate, AUDIO_BITRATE_AS_IS, AUDIO_CHANNELS_AS_IS); + } + + public Android16By9FormatStrategy(int scale, int videoBitrate, int audioBitrate, int audioChannels) { + mScale = scale; + mVideoBitrate = videoBitrate; + mAudioBitrate = audioBitrate; + mAudioChannels = audioChannels; + } + + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); + int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); + int targetLonger = mScale * 16 * 16; + int targetShorter = mScale * 16 * 9; + int longer, shorter, outWidth, outHeight; + if (width >= height) { + longer = width; + shorter = height; + outWidth = targetLonger; + outHeight = targetShorter; + } else { + shorter = width; + longer = height; + outWidth = targetShorter; + outHeight = targetLonger; + } + if (longer * 9 != shorter * 16) { + throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")"); + } + if (shorter <= targetShorter) { + Log.d(TAG, "This video's height is less or equal to " + targetShorter + ", pass-through. (" + width + "x" + height + ")"); + return null; + } + MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight); + // From Nexus 4 Camera in 720p + format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + return format; + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + if (mAudioBitrate == AUDIO_BITRATE_AS_IS || mAudioChannels == AUDIO_CHANNELS_AS_IS) return null; + + // Use original sample rate, as resampling is not supported yet. + final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, + inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); + format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); + format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate); + return format; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java new file mode 100644 index 000000000..dc59caa1d --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaCodecInfo; +import android.media.MediaFormat; +import android.util.Log; + +class Android720pFormatStrategy implements MediaFormatStrategy { + public static final int AUDIO_BITRATE_AS_IS = -1; + public static final int AUDIO_CHANNELS_AS_IS = -1; + private static final String TAG = "720pFormatStrategy"; + private static final int LONGER_LENGTH = 1280; + private static final int SHORTER_LENGTH = 720; + private static final int DEFAULT_VIDEO_BITRATE = 8000 * 1000; // From Nexus 4 Camera in 720p + private final int mVideoBitrate; + private final int mAudioBitrate; + private final int mAudioChannels; + + public Android720pFormatStrategy() { + this(DEFAULT_VIDEO_BITRATE); + } + + public Android720pFormatStrategy(int videoBitrate) { + this(videoBitrate, AUDIO_BITRATE_AS_IS, AUDIO_CHANNELS_AS_IS); + } + + public Android720pFormatStrategy(int videoBitrate, int audioBitrate, int audioChannels) { + mVideoBitrate = videoBitrate; + mAudioBitrate = audioBitrate; + mAudioChannels = audioChannels; + } + + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); + int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); + int longer, shorter, outWidth, outHeight; + if (width >= height) { + longer = width; + shorter = height; + outWidth = LONGER_LENGTH; + outHeight = SHORTER_LENGTH; + } else { + shorter = width; + longer = height; + outWidth = SHORTER_LENGTH; + outHeight = LONGER_LENGTH; + } + if (longer * 9 != shorter * 16) { + throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")"); + } + if (shorter <= SHORTER_LENGTH) { + Log.d(TAG, "This video is less or equal to 720p, pass-through. (" + width + "x" + height + ")"); + return null; + } + MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight); + // From Nexus 4 Camera in 720p + format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + return format; + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + if (mAudioBitrate == AUDIO_BITRATE_AS_IS || mAudioChannels == AUDIO_CHANNELS_AS_IS) return null; + + // Use original sample rate, as resampling is not supported yet. + final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, + inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); + format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); + format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate); + return format; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/ExportPreset960x540Strategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/ExportPreset960x540Strategy.java new file mode 100644 index 000000000..56d9a7d34 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/ExportPreset960x540Strategy.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaFormat; +import android.util.Log; + +/** +* Created by yuya.tanaka on 2014/11/20. +*/ +class ExportPreset960x540Strategy implements MediaFormatStrategy { + private static final String TAG = "ExportPreset960x540Strategy"; + + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + // TODO: detect non-baseline profile and throw exception + int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); + int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); + MediaFormat outputFormat = MediaFormatPresets.getExportPreset960x540(width, height); + int outWidth = outputFormat.getInteger(MediaFormat.KEY_WIDTH); + int outHeight = outputFormat.getInteger(MediaFormat.KEY_HEIGHT); + Log.d(TAG, String.format("inputFormat: %dx%d => outputFormat: %dx%d", width, height, outWidth, outHeight)); + return outputFormat; + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + // TODO + return null; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java new file mode 100644 index 000000000..7f34adc97 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +public class MediaFormatExtraConstants { + // from MediaFormat of API level >= 21, but might be usable in older APIs as native code implementation exists. + // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2621 + // NOTE: native code enforces baseline profile. + // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2638 + /** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCProfile* . */ + public static final String KEY_PROFILE = "profile"; + + // from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2623 + /** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCLevel* . */ + public static final String KEY_LEVEL = "level"; + + // from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/MediaCodec.cpp#2197 + /** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */ + public static final String KEY_AVC_SPS = "csd-0"; + /** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */ + public static final String KEY_AVC_PPS = "csd-1"; + + /** + * For decoder parameter and included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. + * Decoder rotates specified degrees before rendering video to surface. + * NOTE: Only included in track format of API >= 21. + */ + public static final String KEY_ROTATION_DEGREES = "rotation-degrees"; + + // Video formats + // from MediaFormat of API level >= 21 + public static final String MIMETYPE_VIDEO_AVC = "video/avc"; + public static final String MIMETYPE_VIDEO_H263 = "video/3gpp"; + public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8"; + + // Audio formats + // from MediaFormat of API level >= 21 + public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm"; + + private MediaFormatExtraConstants() { + throw new RuntimeException(); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatPresets.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatPresets.java new file mode 100644 index 000000000..086bcd3f3 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatPresets.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaCodecInfo; +import android.media.MediaFormat; + +// Refer for example: https://gist.github.com/wobbals/3990442 +// Refer for preferred parameters: https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html#//apple_ref/doc/uid/TP40008332-CH102-SW8 +// Refer for available keys: (ANDROID ROOT)/media/libstagefright/ACodec.cpp +public class MediaFormatPresets { + private static final int LONGER_LENGTH_960x540 = 960; + + private MediaFormatPresets() { + } + + // preset similar to iOS SDK's AVAssetExportPreset960x540 + @Deprecated + public static MediaFormat getExportPreset960x540() { + MediaFormat format = MediaFormat.createVideoFormat("video/avc", 960, 540); + format.setInteger(MediaFormat.KEY_BIT_RATE, 5500 * 1000); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + return format; + } + + /** + * Preset similar to iOS SDK's AVAssetExportPreset960x540. + * Note that encoding resolutions of this preset are not supported in all devices e.g. Nexus 4. + * On unsupported device encoded video stream will be broken without any exception. + * @param originalWidth Input video width. + * @param originalHeight Input video height. + * @return MediaFormat instance, or null if pass through. + */ + public static MediaFormat getExportPreset960x540(int originalWidth, int originalHeight) { + int longerLength = Math.max(originalWidth, originalHeight); + int shorterLength = Math.min(originalWidth, originalHeight); + + if (longerLength <= LONGER_LENGTH_960x540) return null; // don't upscale + + int residue = LONGER_LENGTH_960x540 * shorterLength % longerLength; + if (residue != 0) { + double ambiguousShorter = (double) LONGER_LENGTH_960x540 * shorterLength / longerLength; + throw new OutputFormatUnavailableException(String.format( + "Could not fit to integer, original: (%d, %d), scaled: (%d, %f)", + longerLength, shorterLength, LONGER_LENGTH_960x540, ambiguousShorter)); + } + + int scaledShorter = LONGER_LENGTH_960x540 * shorterLength / longerLength; + int width, height; + if (originalWidth >= originalHeight) { + width = LONGER_LENGTH_960x540; + height = scaledShorter; + } else { + width = scaledShorter; + height = LONGER_LENGTH_960x540; + } + + MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); + format.setInteger(MediaFormat.KEY_BIT_RATE, 5500 * 1000); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + return format; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategy.java new file mode 100644 index 000000000..1aa8f6dc9 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategy.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaFormat; + +public interface MediaFormatStrategy { + + /** + * Returns preferred video format for encoding. + * + * @param inputFormat MediaFormat from MediaExtractor, contains csd-0/csd-1. + * @return null for passthrough. + * @throws OutputFormatUnavailableException if input could not be transcoded because of restrictions. + */ + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat); + + /** + * Caution: this method should return null currently. + * + * @return null for passthrough. + * @throws OutputFormatUnavailableException if input could not be transcoded because of restrictions. + */ + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat); + +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java new file mode 100644 index 000000000..30802586b --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +public class MediaFormatStrategyPresets { + public static final int AUDIO_BITRATE_AS_IS = -1; + public static final int AUDIO_CHANNELS_AS_IS = -1; + + /** + * @deprecated Use {@link #createExportPreset960x540Strategy()}. + */ + @Deprecated + public static final MediaFormatStrategy EXPORT_PRESET_960x540 = new ExportPreset960x540Strategy(); + + /** + * Preset based on Nexus 4 camera recording with 720p quality. + * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available). + * Default bitrate is 8Mbps. {@link #createAndroid720pStrategy(int)} to specify bitrate. + */ + public static MediaFormatStrategy createAndroid720pStrategy() { + return new Android720pFormatStrategy(); + } + + /** + * Preset based on Nexus 4 camera recording with 720p quality. + * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available). + * Audio track will be copied as-is. + * + * @param bitrate Preferred bitrate for video encoding. + */ + public static MediaFormatStrategy createAndroid720pStrategy(int bitrate) { + return new Android720pFormatStrategy(bitrate); + } + + /** + * Preset based on Nexus 4 camera recording with 720p quality. + * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available). + * <br> + * Note: audio transcoding is experimental feature. + * + * @param bitrate Preferred bitrate for video encoding. + * @param audioBitrate Preferred bitrate for audio encoding. + * @param audioChannels Output audio channels. + */ + public static MediaFormatStrategy createAndroid720pStrategy(int bitrate, int audioBitrate, int audioChannels) { + return new Android720pFormatStrategy(bitrate, audioBitrate, audioChannels); + } + + /** + * Preset similar to iOS SDK's AVAssetExportPreset960x540. + * Note that encoding resolutions of this preset are not supported in all devices e.g. Nexus 4. + * On unsupported device encoded video stream will be broken without any exception. + */ + public static MediaFormatStrategy createExportPreset960x540Strategy() { + return new ExportPreset960x540Strategy(); + } + + private MediaFormatStrategyPresets() { + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/OutputFormatUnavailableException.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/OutputFormatUnavailableException.java new file mode 100644 index 000000000..4fb9b33c9 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/OutputFormatUnavailableException.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +public class OutputFormatUnavailableException extends RuntimeException { + public OutputFormatUnavailableException(String detailMessage) { + super(detailMessage); + } +} |