From 8d5865b68c45358f0d47b58e2577637916fc82fb Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 18:55:10 +0200 Subject: [PATCH 01/12] Rename OutputStrategy to TrackStrategy --- README.md | 30 ++++++------- .../transcoder/demo/TranscoderActivity.java | 13 +++--- .../transcoder/TranscoderOptions.java | 42 +++++++++---------- .../transcoder/engine/TranscoderEngine.java | 14 +++---- .../transcoder/engine/ValidatorException.java | 1 - .../strategy/DefaultAudioStrategy.java | 6 +-- .../strategy/DefaultVideoStrategy.java | 10 ++--- .../strategy/PassThroughTrackStrategy.java | 6 +-- .../strategy/RemoveTrackStrategy.java | 6 +-- ...OutputStrategy.java => TrackStrategy.java} | 6 +-- ...ption.java => TrackStrategyException.java} | 16 +++---- .../transcoder/validator/Validator.java | 3 +- 12 files changed, 75 insertions(+), 78 deletions(-) rename lib/src/main/java/com/otaliastudios/transcoder/strategy/{OutputStrategy.java => TrackStrategy.java} (82%) rename lib/src/main/java/com/otaliastudios/transcoder/strategy/{OutputStrategyException.java => TrackStrategyException.java} (68%) diff --git a/README.md b/README.md index 55f99005..bd237caf 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Take a look at the demo app for a real example or keep reading below for documen - Override frames timestamp, e.g. to slow down the middle part of the video [[docs]](#time-interpolation) - Error handling [[docs]](#listening-for-events) - Configurable validators to e.g. avoid transcoding if the source is already compressed enough [[docs]](#validators) -- Configurable video and audio strategies [[docs]](#output-strategies) +- Configurable video and audio strategies [[docs]](#track-strategies) *This project started as a fork of [ypresto/android-transcoder](https://github.com/ypresto/android-transcoder). With respect to the source project, which misses most of the functionality listed above, @@ -94,7 +94,7 @@ simply `setDataSource(path)` in the transcoding builder. ## Listening for events -Transcoding will happen on a background thread, but we will send updates through the `MediaTranscoder.Listener` +Transcoding will happen on a background thread, but we will send updates through the `TranscoderListener` interface, which can be applied when building the request: ```java @@ -174,7 +174,7 @@ The TrackStatus enum contains the following values: |`TrackStatus.COMPRESSING`|This track is about to be processed and compressed in the target file.| |`TrackStatus.REMOVING`|This track will be removed in the target file.| -The `TrackStatus` value depends on the [output strategy](#output-strategies) that was used. +The `TrackStatus` value depends on the [track strategy](#track-strategies) that was used. We provide a few validators that can be injected for typical usage. #### `DefaultValidator` @@ -193,19 +193,19 @@ A Validator that gives priority to the video track. Transcoding will not happen even if the audio track might need it. If reducing file size is your only concern, this can avoid compressing files that would not benefit so much from compressing the audio track only. -## Output Strategies +## Track Strategies -Output strategies return options for each track (audio or video) for the engine to understand **how** +Track strategies return options for each track (audio or video) for the engine to understand **how** and **if** this track should be transcoded, and whether the whole process should be aborted. ```java Transcoder.into(filePath) - .setVideoOutputStrategy(videoStrategy) - .setAudioOutputStrategy(audioStrategy) + .setVideoTrackStrategy(videoStrategy) + .setAudioTrackStrategy(audioStrategy) // ... ``` -The point of `OutputStrategy` is to inspect the input `android.media.MediaFormat` and return +The point of `TrackStrategy` is to inspect the input `android.media.MediaFormat` and return the output `android.media.MediaFormat`, filled with required options. This library offers track specific strategies that help with audio and video options (see @@ -214,14 +214,14 @@ In addition, we have a few built-in strategies that can work for both audio and #### `PassThroughTrackStrategy` -An OutputStrategy that asks the encoder to keep this track as is, by returning the same input +A TrackStrategy that asks the encoder to keep this track as is, by returning the same input format. Note that this is risky, as the input track format might not be supported my the MP4 container. This will set the `TrackStatus` to `TrackStatus.PASS_THROUGH`. #### `RemoveTrackStrategy` -An OutputStrategy that asks the encoder to remove this track from the output container, by returning null. +A TrackStrategy that asks the encoder to remove this track from the output container, by returning null. For instance, this can be used as an audio strategy to remove audio from video/audio streams. This will set the `TrackStatus` to `TrackStatus.REMOVING`. @@ -233,9 +233,9 @@ audio stream to AAC format with the specified number of channels. ```java Transcoder.into(filePath) - .setAudioOutputStrategy(new DefaultAudioStrategy(1)) // or.. - .setAudioOutputStrategy(new DefaultAudioStrategy(2)) // or.. - .setAudioOutputStrategy(new DefaultAudioStrategy(DefaultAudioStrategy.AUDIO_CHANNELS_AS_IS)) + .setAudioTrackStrategy(new DefaultAudioStrategy(1)) // or.. + .setAudioTrackStrategy(new DefaultAudioStrategy(2)) // or.. + .setAudioTrackStrategy(new DefaultAudioStrategy(DefaultAudioStrategy.AUDIO_CHANNELS_AS_IS)) // ... ``` @@ -406,8 +406,8 @@ We collect common presets in the `DefaultVideoStrategies` class: ```java Transcoder.into(filePath) - .setVideoOutputStrategy(DefaultVideoStrategies.for720x1280()) // 16:9 - .setVideoOutputStrategy(DefaultVideoStrategies.for360x480()) // 4:3 + .setVideoTrackStrategy(DefaultVideoStrategies.for720x1280()) // 16:9 + .setVideoTrackStrategy(DefaultVideoStrategies.for360x480()) // 4:3 // ... ``` diff --git a/demo/src/main/java/com/otaliastudios/transcoder/demo/TranscoderActivity.java b/demo/src/main/java/com/otaliastudios/transcoder/demo/TranscoderActivity.java index 8a76210a..e44495c7 100644 --- a/demo/src/main/java/com/otaliastudios/transcoder/demo/TranscoderActivity.java +++ b/demo/src/main/java/com/otaliastudios/transcoder/demo/TranscoderActivity.java @@ -11,16 +11,13 @@ import com.otaliastudios.transcoder.Transcoder; import com.otaliastudios.transcoder.TranscoderListener; -import com.otaliastudios.transcoder.engine.TrackStatus; import com.otaliastudios.transcoder.internal.Logger; import com.otaliastudios.transcoder.strategy.DefaultAudioStrategy; import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy; -import com.otaliastudios.transcoder.strategy.OutputStrategy; -import com.otaliastudios.transcoder.strategy.RemoveTrackStrategy; +import com.otaliastudios.transcoder.strategy.TrackStrategy; import com.otaliastudios.transcoder.strategy.size.AspectRatioResizer; import com.otaliastudios.transcoder.strategy.size.FractionResizer; import com.otaliastudios.transcoder.strategy.size.PassThroughResizer; -import com.otaliastudios.transcoder.validator.DefaultValidator; import java.io.File; import java.io.IOException; @@ -57,8 +54,8 @@ public class TranscoderActivity extends AppCompatActivity implements private Uri mTranscodeInputUri; private File mTranscodeOutputFile; private long mTranscodeStartTime; - private OutputStrategy mTranscodeVideoStrategy; - private OutputStrategy mTranscodeAudioStrategy; + private TrackStrategy mTranscodeVideoStrategy; + private TrackStrategy mTranscodeAudioStrategy; @Override protected void onCreate(Bundle savedInstanceState) { @@ -186,8 +183,8 @@ private void transcode() { mTranscodeFuture = Transcoder.into(mTranscodeOutputFile.getAbsolutePath()) .setDataSource(this, mTranscodeInputUri) .setListener(this) - .setAudioOutputStrategy(mTranscodeAudioStrategy) - .setVideoOutputStrategy(mTranscodeVideoStrategy) + .setAudioTrackStrategy(mTranscodeAudioStrategy) + .setVideoTrackStrategy(mTranscodeVideoStrategy) .setRotation(rotation) .setSpeed(speed) .transcode(); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/TranscoderOptions.java b/lib/src/main/java/com/otaliastudios/transcoder/TranscoderOptions.java index 324df3d7..2bccb0ce 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/TranscoderOptions.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/TranscoderOptions.java @@ -11,7 +11,7 @@ import com.otaliastudios.transcoder.source.UriDataSource; import com.otaliastudios.transcoder.strategy.DefaultAudioStrategy; import com.otaliastudios.transcoder.strategy.DefaultVideoStrategies; -import com.otaliastudios.transcoder.strategy.OutputStrategy; +import com.otaliastudios.transcoder.strategy.TrackStrategy; import com.otaliastudios.transcoder.stretch.AudioStretcher; import com.otaliastudios.transcoder.stretch.DefaultAudioStretcher; import com.otaliastudios.transcoder.time.DefaultTimeInterpolator; @@ -35,8 +35,8 @@ private TranscoderOptions() {} private String outPath; private DataSource dataSource; - private OutputStrategy audioOutputStrategy; - private OutputStrategy videoOutputStrategy; + private TrackStrategy audioTrackStrategy; + private TrackStrategy videoTrackStrategy; private Validator validator; private int rotation; private TimeInterpolator timeInterpolator; @@ -57,13 +57,13 @@ public DataSource getDataSource() { } @NonNull - public OutputStrategy getAudioOutputStrategy() { - return audioOutputStrategy; + public TrackStrategy getAudioTrackStrategy() { + return audioTrackStrategy; } @NonNull - public OutputStrategy getVideoOutputStrategy() { - return videoOutputStrategy; + public TrackStrategy getVideoTrackStrategy() { + return videoTrackStrategy; } @NonNull @@ -90,8 +90,8 @@ public static class Builder { private DataSource dataSource; private TranscoderListener listener; private Handler listenerHandler; - private OutputStrategy audioOutputStrategy; - private OutputStrategy videoOutputStrategy; + private TrackStrategy audioTrackStrategy; + private TrackStrategy videoTrackStrategy; private Validator validator; private int rotation; private TimeInterpolator timeInterpolator; @@ -133,13 +133,13 @@ public Builder setDataSource(@NonNull Context context, @NonNull Uri uri) { * Sets the audio output strategy. If absent, this defaults to * {@link com.otaliastudios.transcoder.strategy.DefaultAudioStrategy}. * - * @param outputStrategy the desired strategy + * @param trackStrategy the desired strategy * @return this for chaining */ @NonNull @SuppressWarnings("unused") - public Builder setAudioOutputStrategy(@Nullable OutputStrategy outputStrategy) { - this.audioOutputStrategy = outputStrategy; + public Builder setAudioTrackStrategy(@Nullable TrackStrategy trackStrategy) { + this.audioTrackStrategy = trackStrategy; return this; } @@ -147,13 +147,13 @@ public Builder setAudioOutputStrategy(@Nullable OutputStrategy outputStrategy) { * Sets the video output strategy. If absent, this defaults to the 16:9 * strategy returned by {@link DefaultVideoStrategies#for720x1280()}. * - * @param outputStrategy the desired strategy + * @param trackStrategy the desired strategy * @return this for chaining */ @NonNull @SuppressWarnings("unused") - public Builder setVideoOutputStrategy(@Nullable OutputStrategy outputStrategy) { - this.videoOutputStrategy = outputStrategy; + public Builder setVideoTrackStrategy(@Nullable TrackStrategy trackStrategy) { + this.videoTrackStrategy = trackStrategy; return this; } @@ -262,11 +262,11 @@ public TranscoderOptions build() { if (looper == null) looper = Looper.getMainLooper(); listenerHandler = new Handler(looper); } - if (audioOutputStrategy == null) { - audioOutputStrategy = new DefaultAudioStrategy(DefaultAudioStrategy.AUDIO_CHANNELS_AS_IS); + if (audioTrackStrategy == null) { + audioTrackStrategy = new DefaultAudioStrategy(DefaultAudioStrategy.AUDIO_CHANNELS_AS_IS); } - if (videoOutputStrategy == null) { - videoOutputStrategy = DefaultVideoStrategies.for720x1280(); + if (videoTrackStrategy == null) { + videoTrackStrategy = DefaultVideoStrategies.for720x1280(); } if (validator == null) { validator = new DefaultValidator(); @@ -282,8 +282,8 @@ public TranscoderOptions build() { options.dataSource = dataSource; options.outPath = outPath; options.listenerHandler = listenerHandler; - options.audioOutputStrategy = audioOutputStrategy; - options.videoOutputStrategy = videoOutputStrategy; + options.audioTrackStrategy = audioTrackStrategy; + options.videoTrackStrategy = videoTrackStrategy; options.validator = validator; options.rotation = rotation; options.timeInterpolator = timeInterpolator; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java index 76162ab1..7859761f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java @@ -23,8 +23,8 @@ import com.otaliastudios.transcoder.TranscoderOptions; import com.otaliastudios.transcoder.source.DataSource; -import com.otaliastudios.transcoder.strategy.OutputStrategy; -import com.otaliastudios.transcoder.strategy.OutputStrategyException; +import com.otaliastudios.transcoder.strategy.TrackStrategy; +import com.otaliastudios.transcoder.strategy.TrackStrategyException; import com.otaliastudios.transcoder.transcode.AudioTrackTranscoder; import com.otaliastudios.transcoder.transcode.NoOpTrackTranscoder; import com.otaliastudios.transcoder.transcode.PassThroughTrackTranscoder; @@ -185,10 +185,10 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, status = TrackStatus.ABSENT; } else { int index = mTracks.index(type); - OutputStrategy strategy; + TrackStrategy strategy; switch (type) { - case VIDEO: strategy = options.getVideoOutputStrategy(); break; - case AUDIO: strategy = options.getAudioOutputStrategy(); break; + case VIDEO: strategy = options.getVideoTrackStrategy(); break; + case AUDIO: strategy = options.getAudioTrackStrategy(); break; default: throw new RuntimeException("Unknown type: " + type); } try { @@ -207,8 +207,8 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, } status = TrackStatus.COMPRESSING; } - } catch (OutputStrategyException strategyException) { - if (strategyException.getType() == OutputStrategyException.TYPE_ALREADY_COMPRESSED) { + } catch (TrackStrategyException strategyException) { + if (strategyException.getType() == TrackStrategyException.TYPE_ALREADY_COMPRESSED) { // Should not abort, because the other track might need compression. Use a pass through. transcoder = new PassThroughTrackTranscoder(mExtractor, index, muxer, type, options.getTimeInterpolator()); status = TrackStatus.PASS_THROUGH; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/ValidatorException.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/ValidatorException.java index 1ca41e7d..04e551eb 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/ValidatorException.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/ValidatorException.java @@ -15,7 +15,6 @@ */ package com.otaliastudios.transcoder.engine; -import com.otaliastudios.transcoder.strategy.OutputStrategy; import com.otaliastudios.transcoder.validator.Validator; import androidx.annotation.Nullable; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultAudioStrategy.java b/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultAudioStrategy.java index 4a8396be..9dd6669f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultAudioStrategy.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultAudioStrategy.java @@ -9,10 +9,10 @@ import androidx.annotation.Nullable; /** - * An {@link OutputStrategy} for audio that converts it to AAC with the given number + * An {@link TrackStrategy} for audio that converts it to AAC with the given number * of channels. */ -public class DefaultAudioStrategy implements OutputStrategy { +public class DefaultAudioStrategy implements TrackStrategy { public static final int AUDIO_CHANNELS_AS_IS = -1; @@ -24,7 +24,7 @@ public DefaultAudioStrategy(int channels) { @Nullable @Override - public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws OutputStrategyException { + public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws TrackStrategyException { int inputChannels = inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int outputChannels = (channels == AUDIO_CHANNELS_AS_IS) ? inputChannels : channels; final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatConstants.MIMETYPE_AUDIO_AAC, diff --git a/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultVideoStrategy.java b/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultVideoStrategy.java index f2dd9775..51da74f1 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultVideoStrategy.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultVideoStrategy.java @@ -19,10 +19,10 @@ import androidx.annotation.Nullable; /** - * An {@link OutputStrategy} for video that converts it AVC with the given size. + * An {@link TrackStrategy} for video that converts it AVC with the given size. * The input and output aspect ratio must match. */ -public class DefaultVideoStrategy implements OutputStrategy { +public class DefaultVideoStrategy implements TrackStrategy { private final static String TAG = "DefaultVideoStrategy"; private final static Logger LOG = new Logger(TAG); @@ -205,7 +205,7 @@ public DefaultVideoStrategy(@NonNull Options options) { @Nullable @Override - public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws OutputStrategyException { + public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws TrackStrategyException { boolean typeDone = inputFormat.getString(MediaFormat.KEY_MIME).equals(MIME_TYPE); // Compute output size. @@ -217,7 +217,7 @@ public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws O try { outSize = options.resizer.getOutputSize(inSize); } catch (Exception e) { - throw OutputStrategyException.unavailable(e); + throw TrackStrategyException.unavailable(e); } int outWidth, outHeight; if (outSize instanceof ExactSize) { @@ -253,7 +253,7 @@ public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws O // See if we should go on. if (typeDone && sizeDone && frameRateDone && frameIntervalDone) { - throw OutputStrategyException.alreadyCompressed( + throw TrackStrategyException.alreadyCompressed( "Input minSize: " + inSize.getMinor() + ", desired minSize: " + outSize.getMinor() + "\nInput frameRate: " + inputFrameRate + ", desired frameRate: " + outFrameRate + "\nInput iFrameInterval: " + inputIFrameInterval + ", desired iFrameInterval: " + options.targetIFrameInterval); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/strategy/PassThroughTrackStrategy.java b/lib/src/main/java/com/otaliastudios/transcoder/strategy/PassThroughTrackStrategy.java index f19d6782..bcdd4269 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/PassThroughTrackStrategy.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/PassThroughTrackStrategy.java @@ -6,16 +6,16 @@ import androidx.annotation.Nullable; /** - * An {@link OutputStrategy} that asks the encoder to keep this track as is. + * An {@link TrackStrategy} that asks the encoder to keep this track as is. * Note that this is risky, as the track type might not be supported by * the mp4 container. */ @SuppressWarnings("unused") -public class PassThroughTrackStrategy implements OutputStrategy { +public class PassThroughTrackStrategy implements TrackStrategy { @Nullable @Override - public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws OutputStrategyException { + public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws TrackStrategyException { return inputFormat; } } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/strategy/RemoveTrackStrategy.java b/lib/src/main/java/com/otaliastudios/transcoder/strategy/RemoveTrackStrategy.java index c8528f1e..d3515197 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/RemoveTrackStrategy.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/RemoveTrackStrategy.java @@ -6,14 +6,14 @@ import androidx.annotation.Nullable; /** - * An {@link OutputStrategy} that removes this track from output. + * An {@link TrackStrategy} that removes this track from output. */ @SuppressWarnings("unused") -public class RemoveTrackStrategy implements OutputStrategy { +public class RemoveTrackStrategy implements TrackStrategy { @Nullable @Override - public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws OutputStrategyException { + public MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws TrackStrategyException { return null; } } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/strategy/OutputStrategy.java b/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategy.java similarity index 82% rename from lib/src/main/java/com/otaliastudios/transcoder/strategy/OutputStrategy.java rename to lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategy.java index eec5410e..a26293fc 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/OutputStrategy.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategy.java @@ -12,12 +12,12 @@ * Video strategies should use a {@link Resizer} instance to compute the output * video size. */ -public interface OutputStrategy { +public interface TrackStrategy { /** * Create the output format for this track (either audio or video). * Implementors can: - * - throw a {@link OutputStrategyException} if the whole transcoding should be aborted + * - throw a {@link TrackStrategyException} if the whole transcoding should be aborted * - return {@code inputFormat} for remuxing this track as-is * - returning {@code null} for removing this track from output * @@ -25,5 +25,5 @@ public interface OutputStrategy { * @return the output format */ @Nullable - MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws OutputStrategyException; + MediaFormat createOutputFormat(@NonNull MediaFormat inputFormat) throws TrackStrategyException; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/strategy/OutputStrategyException.java b/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategyException.java similarity index 68% rename from lib/src/main/java/com/otaliastudios/transcoder/strategy/OutputStrategyException.java rename to lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategyException.java index 6644e8ac..5a009724 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/OutputStrategyException.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategyException.java @@ -19,12 +19,12 @@ import androidx.annotation.Nullable; /** - * Base class for exceptions thrown by {@link OutputStrategy} by any + * Base class for exceptions thrown by {@link TrackStrategy} by any * strategy implementors. * * These are later caught internally. */ -public class OutputStrategyException extends RuntimeException { +public class TrackStrategyException extends RuntimeException { @SuppressWarnings("WeakerAccess") public final static int TYPE_UNAVAILABLE = 0; @@ -34,13 +34,13 @@ public class OutputStrategyException extends RuntimeException { private int type; @SuppressWarnings("WeakerAccess") - public OutputStrategyException(int type, @Nullable String detailMessage) { + public TrackStrategyException(int type, @Nullable String detailMessage) { super(detailMessage); this.type = type; } @SuppressWarnings("WeakerAccess") - public OutputStrategyException(int type, @Nullable Exception cause) { + public TrackStrategyException(int type, @Nullable Exception cause) { super(cause); this.type = type; } @@ -51,13 +51,13 @@ public int getType() { @NonNull @SuppressWarnings("WeakerAccess") - public static OutputStrategyException unavailable(@Nullable Exception cause) { - return new OutputStrategyException(TYPE_UNAVAILABLE, cause); + public static TrackStrategyException unavailable(@Nullable Exception cause) { + return new TrackStrategyException(TYPE_UNAVAILABLE, cause); } @NonNull @SuppressWarnings("WeakerAccess") - public static OutputStrategyException alreadyCompressed(@Nullable String detailMessage) { - return new OutputStrategyException(TYPE_ALREADY_COMPRESSED, detailMessage); + public static TrackStrategyException alreadyCompressed(@Nullable String detailMessage) { + return new TrackStrategyException(TYPE_ALREADY_COMPRESSED, detailMessage); } } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/validator/Validator.java b/lib/src/main/java/com/otaliastudios/transcoder/validator/Validator.java index 806ac03f..ebd8099c 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/validator/Validator.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/validator/Validator.java @@ -3,10 +3,11 @@ import androidx.annotation.NonNull; import com.otaliastudios.transcoder.engine.TrackStatus; +import com.otaliastudios.transcoder.strategy.TrackStrategy; /** * A validator determines if the transcoding process should proceed or not, - * after the {@link com.otaliastudios.transcoder.strategy.OutputStrategy} have + * after the {@link TrackStrategy} have * provided the output format. */ public interface Validator { From 9d5c63ac3705aaa6559a82f94079d1559520000d Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 19:02:40 +0200 Subject: [PATCH 02/12] Rename internal validator to Checks --- ...atValidator.java => OutputFormatChecks.java} | 11 +++++------ .../transcoder/engine/TranscoderMuxer.java | 17 +++++++---------- 2 files changed, 12 insertions(+), 16 deletions(-) rename lib/src/main/java/com/otaliastudios/transcoder/engine/{MediaFormatValidator.java => OutputFormatChecks.java} (90%) diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/MediaFormatValidator.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java similarity index 90% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/MediaFormatValidator.java rename to lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java index 0f632acf..04a71ac2 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/MediaFormatValidator.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java @@ -24,14 +24,14 @@ import java.nio.ByteBuffer; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -class MediaFormatValidator { - private static final String TAG = "MediaFormatValidator"; +class OutputFormatChecks { + private static final String TAG = OutputFormatChecks.class.getSimpleName(); private static final Logger LOG = new Logger(TAG); - void validateVideoOutputFormat(@Nullable MediaFormat format) { - if (format == null) return; + void checkVideoOutputFormat(@NonNull MediaFormat format) { String mime = format.getString(MediaFormat.KEY_MIME); // Refer: http://developer.android.com/guide/appendix/media-formats.html#core // Refer: http://en.wikipedia.org/wiki/MPEG-4_Part_14#Data_streams @@ -54,8 +54,7 @@ void validateVideoOutputFormat(@Nullable MediaFormat format) { } } - void validateAudioOutputFormat(@Nullable MediaFormat format) { - if (format == null) return; + void checkAudioOutputFormat(@NonNull MediaFormat format) { String mime = format.getString(MediaFormat.KEY_MIME); if (!MediaFormatConstants.MIMETYPE_AUDIO_AAC.equals(mime)) { throw new InvalidOutputFormatException("Audio codecs other than AAC is not supported, actual mime type: " + mime); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java index e50c91b2..0b9feb1c 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java @@ -96,22 +96,19 @@ public void setOutputFormat(@NonNull TrackType trackType, @NonNull MediaFormat f // If both video and audio are ready, validate the formats and go on. // We will stop buffering data and we will start actually muxing it. - if(mVideoTrackNeedsValidation||mAudioTrackNeedsValidation) { - MediaFormatValidator formatValidator = new MediaFormatValidator(); - if(mVideoTrackNeedsValidation) { - formatValidator.validateVideoOutputFormat(videoOutputFormat); - } - if(mAudioTrackNeedsValidation) { - formatValidator.validateAudioOutputFormat(audioOutputFormat); - } - } - + OutputFormatChecks checks = new OutputFormatChecks(); if (isTranscodingVideo) { + if (mVideoTrackNeedsValidation) { + checks.checkVideoOutputFormat(videoOutputFormat); + } int videoIndex = mMuxer.addTrack(videoOutputFormat); mTracks.outputIndex(TrackType.VIDEO, videoIndex); LOG.v("Added track #" + videoIndex + " with " + videoOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); } if (isTranscodingAudio) { + if (mAudioTrackNeedsValidation) { + checks.checkAudioOutputFormat(audioOutputFormat); + } int audioIndex = mMuxer.addTrack(audioOutputFormat); mTracks.outputIndex(TrackType.AUDIO, audioIndex); LOG.v("Added track #" + audioIndex + " with " + audioOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); From 33804deb026a912edd8a0d37d59c8f733528a531 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 19:24:16 +0200 Subject: [PATCH 03/12] Small changes --- .../transcoder/engine/OutputFormatChecks.java | 12 +++++++++-- .../transcoder/engine/TranscoderMuxer.java | 20 ++++--------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java index 04a71ac2..af07c0cc 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java @@ -31,7 +31,15 @@ class OutputFormatChecks { private static final String TAG = OutputFormatChecks.class.getSimpleName(); private static final Logger LOG = new Logger(TAG); - void checkVideoOutputFormat(@NonNull MediaFormat format) { + void checkOutputFormat(@NonNull TrackType type, @NonNull MediaFormat format) { + if (type == TrackType.VIDEO) { + checkVideoOutputFormat(format); + } else if (type == TrackType.AUDIO) { + checkAudioOutputFormat(format); + } + } + + private void checkVideoOutputFormat(@NonNull MediaFormat format) { String mime = format.getString(MediaFormat.KEY_MIME); // Refer: http://developer.android.com/guide/appendix/media-formats.html#core // Refer: http://en.wikipedia.org/wiki/MPEG-4_Part_14#Data_streams @@ -54,7 +62,7 @@ void checkVideoOutputFormat(@NonNull MediaFormat format) { } } - void checkAudioOutputFormat(@NonNull MediaFormat format) { + private void checkAudioOutputFormat(@NonNull MediaFormat format) { String mime = format.getString(MediaFormat.KEY_MIME); if (!MediaFormatConstants.MIMETYPE_AUDIO_AAC.equals(mime)) { throw new InvalidOutputFormatException("Audio codecs other than AAC is not supported, actual mime type: " + mime); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java index 0b9feb1c..1a2b444b 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java @@ -35,8 +35,6 @@ public class TranscoderMuxer { private static final String TAG = TranscoderMuxer.class.getSimpleName(); private static final Logger LOG = new Logger(TAG); - private boolean mAudioTrackNeedsValidation = false; - private boolean mVideoTrackNeedsValidation = false; private static final int BUFFER_SIZE = 64 * 1024; // I have no idea whether this value is appropriate or not... @@ -60,8 +58,9 @@ private void toBufferInfo(@NonNull MediaCodec.BufferInfo bufferInfo, int offset) private final MediaMuxer mMuxer; private final Tracks mTracks; - private boolean mMuxerStarted; + private boolean mMuxerStarted = false; private final List mQueue = new ArrayList<>(); + private final OutputFormatChecks mChecks = new OutputFormatChecks(); private ByteBuffer mQueueBuffer; TranscoderMuxer(@NonNull MediaMuxer muxer, @NonNull Tracks info) { @@ -77,12 +76,8 @@ private void toBufferInfo(@NonNull MediaCodec.BufferInfo bufferInfo, int offset) * @param format the new format */ public void setOutputFormat(@NonNull TrackType trackType, @NonNull MediaFormat format, boolean needsValidation) { + if (needsValidation) mChecks.checkOutputFormat(trackType, format); mTracks.outputFormat(trackType, format); - if(trackType == TrackType.AUDIO) { - mAudioTrackNeedsValidation = needsValidation; - } else if(trackType == TrackType.VIDEO){ - mVideoTrackNeedsValidation = needsValidation; - } // If we have both, go on. boolean isTranscodingVideo = mTracks.status(TrackType.VIDEO).isTranscoding(); @@ -94,21 +89,14 @@ public void setOutputFormat(@NonNull TrackType trackType, @NonNull MediaFormat f if (!isVideoReady || !isAudioReady) return; if (mMuxerStarted) return; - // If both video and audio are ready, validate the formats and go on. + // If both video and audio are ready, we can go on. // We will stop buffering data and we will start actually muxing it. - OutputFormatChecks checks = new OutputFormatChecks(); if (isTranscodingVideo) { - if (mVideoTrackNeedsValidation) { - checks.checkVideoOutputFormat(videoOutputFormat); - } int videoIndex = mMuxer.addTrack(videoOutputFormat); mTracks.outputIndex(TrackType.VIDEO, videoIndex); LOG.v("Added track #" + videoIndex + " with " + videoOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); } if (isTranscodingAudio) { - if (mAudioTrackNeedsValidation) { - checks.checkAudioOutputFormat(audioOutputFormat); - } int audioIndex = mMuxer.addTrack(audioOutputFormat); mTracks.outputIndex(TrackType.AUDIO, audioIndex); LOG.v("Added track #" + audioIndex + " with " + audioOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); From 14f4f14e937ce975c3ea3488ceb03c53cd9ad18f Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 20:23:55 +0200 Subject: [PATCH 04/12] Create DataSink to replace muxer --- .../transcoder/engine/TranscoderEngine.java | 37 ++-- .../transcoder/engine/TranscoderMuxer.java | 154 ------------- .../transcoder/sink/DataSink.java | 35 +++ .../InvalidOutputFormatException.java | 3 +- .../MediaMuxerChecks.java} | 8 +- .../transcoder/sink/MediaMuxerDataSink.java | 202 ++++++++++++++++++ .../transcode/AudioTrackTranscoder.java | 6 +- .../transcode/BaseTrackTranscoder.java | 12 +- .../transcode/PassThroughTrackTranscoder.java | 14 +- .../transcoder/transcode/TrackTranscoder.java | 2 + .../transcode/VideoTrackTranscoder.java | 6 +- 11 files changed, 279 insertions(+), 200 deletions(-) delete mode 100644 lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java create mode 100644 lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java rename lib/src/main/java/com/otaliastudios/transcoder/{engine => sink}/InvalidOutputFormatException.java (93%) rename lib/src/main/java/com/otaliastudios/transcoder/{engine/OutputFormatChecks.java => sink/MediaMuxerChecks.java} (93%) create mode 100644 lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java index 7859761f..212d909f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java @@ -22,6 +22,9 @@ import android.os.Build; import com.otaliastudios.transcoder.TranscoderOptions; +import com.otaliastudios.transcoder.sink.DataSink; +import com.otaliastudios.transcoder.sink.InvalidOutputFormatException; +import com.otaliastudios.transcoder.sink.MediaMuxerDataSink; import com.otaliastudios.transcoder.source.DataSource; import com.otaliastudios.transcoder.strategy.TrackStrategy; import com.otaliastudios.transcoder.strategy.TrackStrategyException; @@ -62,10 +65,10 @@ public interface ProgressCallback { } private DataSource mDataSource; + private DataSink mDataSink; private Map mTranscoders = new HashMap<>(); private Tracks mTracks; private MediaExtractor mExtractor; - private MediaMuxer mMuxer; private volatile double mProgress; private ProgressCallback mProgressCallback; private long mDurationUs; @@ -109,11 +112,11 @@ public void transcode(@NonNull TranscoderOptions options) throws IOException, In // NOTE: use single extractor to keep from running out audio track fast. mExtractor = new MediaExtractor(); mDataSource.apply(mExtractor); - mMuxer = new MediaMuxer(options.getOutputPath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + mDataSink = new MediaMuxerDataSink(options.getOutputPath()); setUpMetadata(options); setupTrackTranscoders(options); runPipelines(); - mMuxer.stop(); + mDataSink.stop(); } finally { try { TrackTranscoder videoTranscoder = mTranscoders.get(TrackType.VIDEO); @@ -131,14 +134,7 @@ public void transcode(@NonNull TranscoderOptions options) throws IOException, In //noinspection ThrowFromFinallyBlock throw new Error("Could not shutdown extractor, codecs and muxer pipeline.", e); } - try { - if (mMuxer != null) { - mMuxer.release(); - mMuxer = null; - } - } catch (RuntimeException e) { - LOG.e("Failed to release muxer.", e); - } + mDataSink.release(); } } @@ -151,14 +147,14 @@ private void setUpMetadata(@NonNull TranscoderOptions options) { try { rotation = Integer.parseInt(rotationString); } catch (NumberFormatException ignore) {} - mMuxer.setOrientationHint((rotation + options.getRotation()) % 360); + mDataSink.setOrientation((rotation + options.getRotation()) % 360); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { String locationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); if (locationString != null) { float[] location = new ISO6709LocationParser().parse(locationString); if (location != null) { - mMuxer.setLocation(location[0], location[1]); + mDataSink.setLocation(location[0], location[1]); } else { LOG.v("Failed to parse the location metadata: " + locationString); } @@ -174,7 +170,6 @@ private void setUpMetadata(@NonNull TranscoderOptions options) { } private void setUpTrackTranscoder(@NonNull TranscoderOptions options, - @NonNull TranscoderMuxer muxer, @NonNull TrackType type) { TrackStatus status; TrackTranscoder transcoder; @@ -197,12 +192,12 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, transcoder = new NoOpTrackTranscoder(); status = TrackStatus.REMOVING; } else if (outputFormat == inputFormat) { - transcoder = new PassThroughTrackTranscoder(mExtractor, index, muxer, type, options.getTimeInterpolator()); + transcoder = new PassThroughTrackTranscoder(mExtractor, index, mDataSink, type, options.getTimeInterpolator()); status = TrackStatus.PASS_THROUGH; } else { switch (type) { - case VIDEO: transcoder = new VideoTrackTranscoder(mExtractor, muxer, index, options.getTimeInterpolator()); break; - case AUDIO: transcoder = new AudioTrackTranscoder(mExtractor, muxer, index, options.getTimeInterpolator(), options.getAudioStretcher()); break; + case VIDEO: transcoder = new VideoTrackTranscoder(mExtractor, mDataSink, index, options.getTimeInterpolator()); break; + case AUDIO: transcoder = new AudioTrackTranscoder(mExtractor, mDataSink, index, options.getTimeInterpolator(), options.getAudioStretcher()); break; default: throw new RuntimeException("Unknown type: " + type); } status = TrackStatus.COMPRESSING; @@ -210,7 +205,7 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, } catch (TrackStrategyException strategyException) { if (strategyException.getType() == TrackStrategyException.TYPE_ALREADY_COMPRESSED) { // Should not abort, because the other track might need compression. Use a pass through. - transcoder = new PassThroughTrackTranscoder(mExtractor, index, muxer, type, options.getTimeInterpolator()); + transcoder = new PassThroughTrackTranscoder(mExtractor, index, mDataSink, type, options.getTimeInterpolator()); status = TrackStatus.PASS_THROUGH; } else { // Abort. throw strategyException; @@ -218,6 +213,7 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, } } mTracks.status(type, status); + mDataSink.setTrackStatus(type, status); // Just to respect nullability in setUp(). if (outputFormat == null) outputFormat = inputFormat; transcoder.setUp(outputFormat); @@ -226,9 +222,8 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, private void setupTrackTranscoders(@NonNull TranscoderOptions options) { mTracks = Tracks.create(mExtractor); - TranscoderMuxer muxer = new TranscoderMuxer(mMuxer, mTracks); - setUpTrackTranscoder(options, muxer, TrackType.VIDEO); - setUpTrackTranscoder(options, muxer, TrackType.AUDIO); + setUpTrackTranscoder(options, TrackType.VIDEO); + setUpTrackTranscoder(options, TrackType.AUDIO); TrackStatus videoStatus = mTracks.status(TrackType.VIDEO); TrackStatus audioStatus = mTracks.status(TrackType.AUDIO); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java deleted file mode 100644 index 1a2b444b..00000000 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderMuxer.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2015 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 com.otaliastudios.transcoder.engine; - -import android.media.MediaCodec; -import android.media.MediaFormat; -import android.media.MediaMuxer; - -import com.otaliastudios.transcoder.internal.Logger; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; - -import androidx.annotation.NonNull; - -/** - * This class queues until all output track formats are determined. - */ -public class TranscoderMuxer { - - private static final String TAG = TranscoderMuxer.class.getSimpleName(); - private static final Logger LOG = new Logger(TAG); - - private static final int BUFFER_SIZE = 64 * 1024; // I have no idea whether this value is appropriate or not... - - private static class QueuedSample { - private final TrackType mTrackType; - private final int mSize; - private final long mPresentationTimeUs; - private final int mFlags; - - private QueuedSample(@NonNull TrackType trackType, @NonNull MediaCodec.BufferInfo bufferInfo) { - mTrackType = trackType; - mSize = bufferInfo.size; - mPresentationTimeUs = bufferInfo.presentationTimeUs; - mFlags = bufferInfo.flags; - } - - private void toBufferInfo(@NonNull MediaCodec.BufferInfo bufferInfo, int offset) { - bufferInfo.set(offset, mSize, mPresentationTimeUs, mFlags); - } - } - - private final MediaMuxer mMuxer; - private final Tracks mTracks; - private boolean mMuxerStarted = false; - private final List mQueue = new ArrayList<>(); - private final OutputFormatChecks mChecks = new OutputFormatChecks(); - private ByteBuffer mQueueBuffer; - - TranscoderMuxer(@NonNull MediaMuxer muxer, @NonNull Tracks info) { - mMuxer = muxer; - mTracks = info; - } - - /** - * Called by {@link com.otaliastudios.transcoder.transcode.TrackTranscoder}s - * anytime the encoder output format changes (might actually be just once). - * - * @param trackType the sample type, either audio or video - * @param format the new format - */ - public void setOutputFormat(@NonNull TrackType trackType, @NonNull MediaFormat format, boolean needsValidation) { - if (needsValidation) mChecks.checkOutputFormat(trackType, format); - mTracks.outputFormat(trackType, format); - - // If we have both, go on. - boolean isTranscodingVideo = mTracks.status(TrackType.VIDEO).isTranscoding(); - boolean isTranscodingAudio = mTracks.status(TrackType.AUDIO).isTranscoding(); - MediaFormat videoOutputFormat = mTracks.outputFormat(TrackType.VIDEO); - MediaFormat audioOutputFormat = mTracks.outputFormat(TrackType.AUDIO); - boolean isVideoReady = videoOutputFormat != null || !isTranscodingVideo; - boolean isAudioReady = audioOutputFormat != null || !isTranscodingAudio; - if (!isVideoReady || !isAudioReady) return; - if (mMuxerStarted) return; - - // If both video and audio are ready, we can go on. - // We will stop buffering data and we will start actually muxing it. - if (isTranscodingVideo) { - int videoIndex = mMuxer.addTrack(videoOutputFormat); - mTracks.outputIndex(TrackType.VIDEO, videoIndex); - LOG.v("Added track #" + videoIndex + " with " + videoOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); - } - if (isTranscodingAudio) { - int audioIndex = mMuxer.addTrack(audioOutputFormat); - mTracks.outputIndex(TrackType.AUDIO, audioIndex); - LOG.v("Added track #" + audioIndex + " with " + audioOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); - } - mMuxer.start(); - mMuxerStarted = true; - drainQueue(); - } - - public void write(@NonNull TrackType type, @NonNull ByteBuffer byteBuf, @NonNull MediaCodec.BufferInfo bufferInfo) { - if (mMuxerStarted) { - mMuxer.writeSampleData(mTracks.outputIndex(type), byteBuf, bufferInfo); - } else { - enqueue(type, byteBuf, bufferInfo); - } - } - - /** - * Enqueues the given byffer by writing it into our own buffer and - * just storing its position and size. - * @param type sample type - * @param buffer input buffer - * @param bufferInfo input buffer info - */ - private void enqueue(@NonNull TrackType type, @NonNull ByteBuffer buffer, @NonNull MediaCodec.BufferInfo bufferInfo) { - if (mQueueBuffer == null) { - mQueueBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE).order(ByteOrder.nativeOrder()); - } - buffer.limit(bufferInfo.offset + bufferInfo.size); - buffer.position(bufferInfo.offset); - mQueueBuffer.put(buffer); - mQueue.add(new QueuedSample(type, bufferInfo)); - } - - /** - * Writes all enqueued samples into the muxer, now that it is - * open and running. - */ - private void drainQueue() { - if (mQueue.isEmpty()) return; - mQueueBuffer.flip(); - LOG.i("Output format determined, writing pending data into the muxer. " - + "samples:" + mQueue.size() + " " - + "bytes:" + mQueueBuffer.limit()); - MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - int offset = 0; - for (QueuedSample sample : mQueue) { - sample.toBufferInfo(bufferInfo, offset); - write(sample.mTrackType, mQueueBuffer, bufferInfo); - offset += sample.mSize; - } - mQueue.clear(); - mQueueBuffer = null; - } -} diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java new file mode 100644 index 00000000..a9f65e4e --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java @@ -0,0 +1,35 @@ +package com.otaliastudios.transcoder.sink; + +import android.media.MediaCodec; +import android.media.MediaFormat; + +import androidx.annotation.NonNull; + +import com.otaliastudios.transcoder.engine.TrackStatus; +import com.otaliastudios.transcoder.engine.TrackType; +import com.otaliastudios.transcoder.transcode.TrackTranscoder; + +import java.nio.ByteBuffer; + +public interface DataSink { + + void setOrientation(int rotation); + + void setLocation(double latitude, double longitude); + + void setTrackStatus(@NonNull TrackType type, + @NonNull TrackStatus status); + + void setTrackOutputFormat(@NonNull TrackTranscoder transcoder, + @NonNull TrackType type, + @NonNull MediaFormat format); + + void write(@NonNull TrackTranscoder transcoder, + @NonNull TrackType type, + @NonNull ByteBuffer byteBuffer, + @NonNull MediaCodec.BufferInfo bufferInfo); + + void stop(); + + void release(); +} diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/InvalidOutputFormatException.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/InvalidOutputFormatException.java similarity index 93% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/InvalidOutputFormatException.java rename to lib/src/main/java/com/otaliastudios/transcoder/sink/InvalidOutputFormatException.java index c3093a7e..93f841d5 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/InvalidOutputFormatException.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/InvalidOutputFormatException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.otaliastudios.transcoder.engine; +package com.otaliastudios.transcoder.sink; import androidx.annotation.NonNull; @@ -26,7 +26,6 @@ * {@link Transcoder#transcode(TranscoderOptions)}, which means it can be * passed to {@link TranscoderListener#onTranscodeFailed(Throwable)}. */ -@SuppressWarnings("WeakerAccess") public class InvalidOutputFormatException extends RuntimeException { InvalidOutputFormatException(@NonNull String detailMessage) { super(detailMessage); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerChecks.java similarity index 93% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java rename to lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerChecks.java index af07c0cc..06813d4b 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/OutputFormatChecks.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerChecks.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.otaliastudios.transcoder.engine; +package com.otaliastudios.transcoder.sink; import android.media.MediaFormat; +import com.otaliastudios.transcoder.engine.TrackType; import com.otaliastudios.transcoder.internal.Logger; import com.otaliastudios.transcoder.internal.MediaFormatConstants; import com.otaliastudios.transcoder.engine.internal.AvcCsdUtils; @@ -25,10 +26,9 @@ import java.nio.ByteBuffer; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -class OutputFormatChecks { - private static final String TAG = OutputFormatChecks.class.getSimpleName(); +class MediaMuxerChecks { + private static final String TAG = MediaMuxerChecks.class.getSimpleName(); private static final Logger LOG = new Logger(TAG); void checkOutputFormat(@NonNull TrackType type, @NonNull MediaFormat format) { diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java new file mode 100644 index 00000000..f03d3bb6 --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java @@ -0,0 +1,202 @@ +package com.otaliastudios.transcoder.sink; + +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.media.MediaMuxer; +import android.os.Build; + +import androidx.annotation.NonNull; + +import com.otaliastudios.transcoder.engine.TrackStatus; +import com.otaliastudios.transcoder.engine.TrackType; +import com.otaliastudios.transcoder.internal.Logger; +import com.otaliastudios.transcoder.transcode.PassThroughTrackTranscoder; +import com.otaliastudios.transcoder.transcode.TrackTranscoder; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MediaMuxerDataSink implements DataSink { + + /** + * A queued sample is a sample that we haven't written yet because + * the muxer is still being started (waiting for output formats). + */ + private static class QueuedSample { + private final TrackTranscoder mTranscoder; + private final TrackType mType; + private final int mSize; + private final long mTimeUs; + private final int mFlags; + + private QueuedSample(@NonNull TrackTranscoder transcoder, + @NonNull TrackType type, + @NonNull MediaCodec.BufferInfo bufferInfo) { + mTranscoder = transcoder; + mType = type; + mSize = bufferInfo.size; + mTimeUs = bufferInfo.presentationTimeUs; + mFlags = bufferInfo.flags; + } + } + + private final static String TAG = MediaMuxerDataSink.class.getSimpleName(); + private final static Logger LOG = new Logger(TAG); + + // I have no idea whether this value is appropriate or not... + private final static int BUFFER_SIZE = 64 * 1024; + + private boolean mMuxerStarted = false; + private final MediaMuxer mMuxer; + private final List mQueue = new ArrayList<>(); + private ByteBuffer mQueueBuffer; + private Map mStatus = new HashMap<>(); + private Map mLastFormat = new HashMap<>(); + private Map mMuxerIndex = new HashMap<>(); + private final MediaMuxerChecks mMuxerChecks = new MediaMuxerChecks(); + + public MediaMuxerDataSink(@NonNull String outputFilePath) { + try { + mMuxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setOrientation(int rotation) { + mMuxer.setOrientationHint(rotation); + } + + @Override + public void setLocation(double latitude, double longitude) { + if (Build.VERSION.SDK_INT >= 19) { + mMuxer.setLocation((float) latitude, (float) longitude); + } + } + + @Override + public void setTrackStatus(@NonNull TrackType type, @NonNull TrackStatus status) { + mStatus.put(type, status); + } + + /** + * Called by {@link com.otaliastudios.transcoder.transcode.TrackTranscoder}s + * anytime the encoder output format changes (might actually be just once). + */ + @Override + public void setTrackOutputFormat(@NonNull TrackTranscoder transcoder, + @NonNull TrackType type, + @NonNull MediaFormat format) { + //noinspection ConstantConditions + boolean shouldValidate = mStatus.get(type).isTranscoding() + && !(transcoder instanceof PassThroughTrackTranscoder); + if (shouldValidate) { + mMuxerChecks.checkOutputFormat(type, format); + } + mLastFormat.put(type, format); + startIfNeeded(); + + } + + private void startIfNeeded() { + if (mMuxerStarted) return; + //noinspection ConstantConditions + boolean isTranscodingVideo = mStatus.get(TrackType.VIDEO).isTranscoding(); + //noinspection ConstantConditions + boolean isTranscodingAudio = mStatus.get(TrackType.AUDIO).isTranscoding(); + MediaFormat videoOutputFormat = mLastFormat.get(TrackType.VIDEO); + MediaFormat audioOutputFormat = mLastFormat.get(TrackType.AUDIO); + boolean isVideoReady = videoOutputFormat != null || !isTranscodingVideo; + boolean isAudioReady = audioOutputFormat != null || !isTranscodingAudio; + if (!isVideoReady || !isAudioReady) return; + + // If both video and audio are ready, we can go on. + // We will stop buffering data and we will start actually muxing it. + if (isTranscodingVideo) { + int videoIndex = mMuxer.addTrack(videoOutputFormat); + mMuxerIndex.put(TrackType.VIDEO, videoIndex); + LOG.v("Added track #" + videoIndex + " with " + videoOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); + } + if (isTranscodingAudio) { + int audioIndex = mMuxer.addTrack(audioOutputFormat); + mMuxerIndex.put(TrackType.AUDIO, audioIndex); + LOG.v("Added track #" + audioIndex + " with " + audioOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); + } + mMuxer.start(); + mMuxerStarted = true; + drainQueue(); + } + + @Override + public void write(@NonNull TrackTranscoder transcoder, @NonNull TrackType type, @NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.BufferInfo bufferInfo) { + if (mMuxerStarted) { + //noinspection ConstantConditions + mMuxer.writeSampleData(mMuxerIndex.get(type), byteBuffer, bufferInfo); + } else { + enqueue(transcoder, type, byteBuffer, bufferInfo); + } + } + + /** + * Enqueues the given byffer by writing it into our own buffer and + * just storing its position and size. + * + * @param transcoder transcoder + * @param type track type + * @param buffer input buffer + * @param bufferInfo input buffer info + */ + private void enqueue(@NonNull TrackTranscoder transcoder, + @NonNull TrackType type, + @NonNull ByteBuffer buffer, + @NonNull MediaCodec.BufferInfo bufferInfo) { + if (mQueueBuffer == null) { + mQueueBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE).order(ByteOrder.nativeOrder()); + } + buffer.limit(bufferInfo.offset + bufferInfo.size); + buffer.position(bufferInfo.offset); + mQueueBuffer.put(buffer); + mQueue.add(new QueuedSample(transcoder, type, bufferInfo)); + } + + /** + * Writes all enqueued samples into the muxer, now that it is + * open and running. + */ + private void drainQueue() { + if (mQueue.isEmpty()) return; + mQueueBuffer.flip(); + LOG.i("Output format determined, writing pending data into the muxer. " + + "samples:" + mQueue.size() + " " + + "bytes:" + mQueueBuffer.limit()); + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + int offset = 0; + for (QueuedSample sample : mQueue) { + bufferInfo.set(offset, sample.mSize, sample.mTimeUs, sample.mFlags); + write(sample.mTranscoder, sample.mType, mQueueBuffer, bufferInfo); + offset += sample.mSize; + } + mQueue.clear(); + mQueueBuffer = null; + } + + @Override + public void stop() { + mMuxer.stop(); // If this fails, let's throw. + } + + @Override + public void release() { + try { + mMuxer.release(); + } catch (Exception e) { + LOG.w("Failed to release the muxer.", e); + } + } +} diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java index 740bf3ea..0ccd5fd4 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java @@ -7,8 +7,8 @@ import androidx.annotation.NonNull; import com.otaliastudios.transcoder.engine.TrackType; -import com.otaliastudios.transcoder.engine.TranscoderMuxer; import com.otaliastudios.transcoder.internal.MediaCodecBuffers; +import com.otaliastudios.transcoder.sink.DataSink; import com.otaliastudios.transcoder.stretch.AudioStretcher; import com.otaliastudios.transcoder.time.TimeInterpolator; import com.otaliastudios.transcoder.transcode.internal.AudioEngine; @@ -24,11 +24,11 @@ public class AudioTrackTranscoder extends BaseTrackTranscoder { private MediaFormat mEncoderOutputFormat; // to create the channel public AudioTrackTranscoder(@NonNull MediaExtractor extractor, - @NonNull TranscoderMuxer muxer, + @NonNull DataSink dataSink, int trackIndex, @NonNull TimeInterpolator timeInterpolator, @NonNull AudioStretcher audioStretcher) { - super(extractor, muxer, TrackType.AUDIO, trackIndex); + super(extractor, dataSink, TrackType.AUDIO, trackIndex); mTimeInterpolator = timeInterpolator; mAudioStretcher = audioStretcher; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java index e06cc069..3f890c3d 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java @@ -8,8 +8,8 @@ import androidx.annotation.NonNull; import com.otaliastudios.transcoder.engine.TrackType; -import com.otaliastudios.transcoder.engine.TranscoderMuxer; import com.otaliastudios.transcoder.internal.MediaCodecBuffers; +import com.otaliastudios.transcoder.sink.DataSink; import java.io.IOException; import java.nio.ByteBuffer; @@ -26,7 +26,7 @@ public abstract class BaseTrackTranscoder implements TrackTranscoder { private final MediaExtractor mExtractor; private final int mTrackIndex; - private final TranscoderMuxer mMuxer; + private final DataSink mDataSink; private final TrackType mTrackType; private long mLastPresentationTimeUs; @@ -46,12 +46,12 @@ public abstract class BaseTrackTranscoder implements TrackTranscoder { @SuppressWarnings("WeakerAccess") protected BaseTrackTranscoder(@NonNull MediaExtractor extractor, - @NonNull TranscoderMuxer muxer, + @NonNull DataSink dataSink, @NonNull TrackType trackType, int trackIndex) { mExtractor = extractor; mTrackIndex = trackIndex; - mMuxer = muxer; + mDataSink = dataSink; mTrackType = trackType; } @@ -196,7 +196,7 @@ protected void onEncoderOutputFormatChanged(@NonNull MediaCodec encoder, @NonNul throw new RuntimeException("Audio output format changed twice."); } mActualOutputFormat = format; - mMuxer.setOutputFormat(mTrackType, mActualOutputFormat, true); + mDataSink.setTrackOutputFormat(this, mTrackType, mActualOutputFormat); } @SuppressWarnings("SameParameterValue") @@ -284,7 +284,7 @@ private int drainEncoder(long timeoutUs) { mEncoder.releaseOutputBuffer(result, false); return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; } - mMuxer.write(mTrackType, mEncoderBuffers.getOutputBuffer(result), mBufferInfo); + mDataSink.write(this, mTrackType, mEncoderBuffers.getOutputBuffer(result), mBufferInfo); mEncoder.releaseOutputBuffer(result, false); return DRAIN_STATE_CONSUMED; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java index 074077ba..db76ae91 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java @@ -23,7 +23,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.transcoder.engine.TrackType; -import com.otaliastudios.transcoder.engine.TranscoderMuxer; +import com.otaliastudios.transcoder.sink.DataSink; import com.otaliastudios.transcoder.time.TimeInterpolator; import java.nio.ByteBuffer; @@ -32,7 +32,7 @@ public class PassThroughTrackTranscoder implements TrackTranscoder { private final MediaExtractor mExtractor; private final int mTrackIndex; - private final TranscoderMuxer mMuxer; + private final DataSink mDataSink; private final TrackType mTrackType; private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); private int mBufferSize; @@ -48,12 +48,12 @@ public class PassThroughTrackTranscoder implements TrackTranscoder { public PassThroughTrackTranscoder(@NonNull MediaExtractor extractor, int trackIndex, - @NonNull TranscoderMuxer muxer, + @NonNull DataSink dataSink, @NonNull TrackType trackType, @NonNull TimeInterpolator timeInterpolator) { mExtractor = extractor; mTrackIndex = trackIndex; - mMuxer = muxer; + mDataSink = dataSink; mTrackType = trackType; mOutputFormat = mExtractor.getTrackFormat(mTrackIndex); @@ -71,14 +71,14 @@ public void setUp(@NonNull MediaFormat desiredOutputFormat) { } public boolean transcode() { if (mIsEOS) return false; if (!mOutputFormatSet) { - mMuxer.setOutputFormat(mTrackType, mOutputFormat, false); + mDataSink.setTrackOutputFormat(this, mTrackType, mOutputFormat); mOutputFormatSet = true; } int trackIndex = mExtractor.getSampleTrackIndex(); if (trackIndex < 0) { mBuffer.clear(); mBufferInfo.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); - mMuxer.write(mTrackType, mBuffer, mBufferInfo); + mDataSink.write(this, mTrackType, mBuffer, mBufferInfo); mIsEOS = true; return true; } @@ -92,7 +92,7 @@ public boolean transcode() { long realTimestampUs = mExtractor.getSampleTime(); long timestampUs = mTimeInterpolator.interpolate(mTrackType, realTimestampUs); mBufferInfo.set(0, sampleSize, timestampUs, flags); - mMuxer.write(mTrackType, mBuffer, mBufferInfo); + mDataSink.write(this, mTrackType, mBuffer, mBufferInfo); mLastPresentationTime = realTimestampUs; mExtractor.advance(); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/TrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/TrackTranscoder.java index 7be88bd4..9d42b20f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/TrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/TrackTranscoder.java @@ -20,6 +20,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.otaliastudios.transcoder.engine.TrackType; + public interface TrackTranscoder { void setUp(@NonNull MediaFormat desiredOutputFormat); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java index c58440d7..4ea0549f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java @@ -22,8 +22,8 @@ import androidx.annotation.NonNull; import com.otaliastudios.transcoder.engine.TrackType; -import com.otaliastudios.transcoder.engine.TranscoderMuxer; import com.otaliastudios.transcoder.internal.MediaCodecBuffers; +import com.otaliastudios.transcoder.sink.DataSink; import com.otaliastudios.transcoder.time.TimeInterpolator; import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput; import com.otaliastudios.transcoder.transcode.internal.VideoEncoderInput; @@ -48,10 +48,10 @@ public class VideoTrackTranscoder extends BaseTrackTranscoder { public VideoTrackTranscoder( @NonNull MediaExtractor extractor, - @NonNull TranscoderMuxer muxer, + @NonNull DataSink dataSink, int trackIndex, @NonNull TimeInterpolator timeInterpolator) { - super(extractor, muxer, TrackType.VIDEO, trackIndex); + super(extractor, dataSink, TrackType.VIDEO, trackIndex); mTimeInterpolator = timeInterpolator; } From 3f8d49e438e8c690ac978492d5abdb348c15ce6c Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 20:32:17 +0200 Subject: [PATCH 05/12] Add comments --- .../transcoder/sink/DataSink.java | 52 ++++++++++++++++++- .../transcoder/sink/MediaMuxerDataSink.java | 11 ++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java index a9f65e4e..b92c69c4 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/DataSink.java @@ -11,25 +11,75 @@ import java.nio.ByteBuffer; +/** + * A DataSink is an abstract representation of an encoded data collector. + * Currently the only implementation is {@link MediaMuxerDataSink} which collects + * data into a {@link java.io.File} using {@link android.media.MediaMuxer}. + * + * However there might be other implementations in the future, for example to stream data + * to a server. + */ public interface DataSink { - void setOrientation(int rotation); + /** + * Called before starting to set the orientation metadata. + * @param orientation 0, 90, 180 or 270 + */ + void setOrientation(int orientation); + /** + * Called before starting to set the location metadata. + * @param latitude latitude + * @param longitude longitude + */ void setLocation(double latitude, double longitude); + /** + * Called before starting to set the status for the given + * track. The sink object can check if the track is transcoding + * using {@link TrackStatus#isTranscoding()}. + * + * @param type track type + * @param status status + */ void setTrackStatus(@NonNull TrackType type, @NonNull TrackStatus status); + /** + * Called by {@link TrackTranscoder}s when they have an output format. + * This is not the output format chosen by the library user but rather the + * output format determined by {@link MediaCodec}, which contains more information, + * and should be inspected to know what kind of data we're collecting. + * + * @param transcoder the transcoder + * @param type the track type + * @param format the track format + */ void setTrackOutputFormat(@NonNull TrackTranscoder transcoder, @NonNull TrackType type, @NonNull MediaFormat format); + /** + * Called by {@link TrackTranscoder}s to write data into this sink. + * + * @param transcoder the transcoder + * @param type the track type + * @param byteBuffer the data + * @param bufferInfo the metadata + */ void write(@NonNull TrackTranscoder transcoder, @NonNull TrackType type, @NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.BufferInfo bufferInfo); + /** + * Called when transcoders have stopped writing. + */ void stop(); + /** + * Called to release resources. + * Any exception should probably be caught here. + */ void release(); } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java index f03d3bb6..7aec8368 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java @@ -21,6 +21,13 @@ import java.util.List; import java.util.Map; + +/** + * A {@link DataSink} implementation that: + * + * - Uses {@link MediaMuxer} to collect data + * - Creates an output file with the readable media + */ public class MediaMuxerDataSink implements DataSink { /** @@ -85,10 +92,6 @@ public void setTrackStatus(@NonNull TrackType type, @NonNull TrackStatus status) mStatus.put(type, status); } - /** - * Called by {@link com.otaliastudios.transcoder.transcode.TrackTranscoder}s - * anytime the encoder output format changes (might actually be just once). - */ @Override public void setTrackOutputFormat(@NonNull TrackTranscoder transcoder, @NonNull TrackType type, From 52e90314ff954b13da6f7250f4dfcc8d7744fc65 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 20:43:48 +0200 Subject: [PATCH 06/12] Create TrackTypeMap --- .../transcoder/engine/TrackTypeMap.java | 42 ++++++++++ .../transcoder/engine/Tracks.java | 78 ++++--------------- .../transcoder/engine/TranscoderEngine.java | 20 ++--- .../transcoder/sink/MediaMuxerDataSink.java | 26 +++---- .../time/SpeedTimeInterpolator.java | 11 +-- 5 files changed, 81 insertions(+), 96 deletions(-) create mode 100644 lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java new file mode 100644 index 00000000..342367d8 --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java @@ -0,0 +1,42 @@ +package com.otaliastudios.transcoder.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * An utility class for storing data relative to a single {@link TrackType} + * in a map, with handy nullability annotations. + * + * @param the map type + */ +public class TrackTypeMap { + + private Map map = new HashMap<>(); + + public void set(@NonNull TrackType type, @NonNull T value) { + map.put(type, value); + } + + @Nullable + public T get(@NonNull TrackType type) { + return map.get(type); + } + + @NonNull + public T require(@NonNull TrackType type) { + //noinspection ConstantConditions + return map.get(type); + } + + @SuppressWarnings("WeakerAccess") + public void clear() { + map.clear(); + } + + public boolean has(@NonNull TrackType type) { + return map.containsKey(type); + } +} diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java index ba524146..58a067bb 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java @@ -19,10 +19,6 @@ import android.media.MediaFormat; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; /** * Contains information about the tracks as read from the {@link MediaExtractor} @@ -33,12 +29,10 @@ class Tracks { private Tracks() { } - private Map index = new HashMap<>(); - private Map outputIndex = new HashMap<>(); - private Map mimeType = new HashMap<>(); - private Map format = new HashMap<>(); - private Map outputFormat = new HashMap<>(); - private Map status = new HashMap<>(); + private TrackTypeMap index = new TrackTypeMap<>(); + private TrackTypeMap mimeType = new TrackTypeMap<>(); + private TrackTypeMap format = new TrackTypeMap<>(); + private TrackTypeMap status = new TrackTypeMap<>(); /** * The index in the input file. @@ -49,25 +43,6 @@ int index(@NonNull TrackType type) { return index.get(type); } - /** - * The index in the output file. - * Must be set with {@link #outputIndex(TrackType, int)}. - * @param type track type - * @return index - */ - int outputIndex(@NonNull TrackType type) { - return outputIndex.get(type); - } - - /** - * Sets the index in the output file. - * @param type track type - * @param index index - */ - void outputIndex(@NonNull TrackType type, int index) { - outputIndex.put(type, index); - } - /** * The mime type in the input file. * @param type track type @@ -76,7 +51,7 @@ void outputIndex(@NonNull TrackType type, int index) { @SuppressWarnings("unused") @NonNull String mimeType(@NonNull TrackType type) { - return mimeType.get(type); + return mimeType.require(type); } /** @@ -86,28 +61,7 @@ String mimeType(@NonNull TrackType type) { */ @NonNull MediaFormat format(@NonNull TrackType type) { - return format.get(type); - } - - /** - * The format in the output file. This is nullable! - * Must be set using {@link #outputFormat(TrackType, MediaFormat)} - * when we know the actual output format, as returned by MediaCodec. - * @param type track type - * @return output format - */ - @Nullable - MediaFormat outputFormat(@NonNull TrackType type) { - return outputFormat.get(type); - } - - /** - * Sets the format in the output file. - * @param type track type - * @param format output format - */ - void outputFormat(@NonNull TrackType type, @NonNull MediaFormat format) { - outputFormat.put(type, format); + return format.require(type); } /** @@ -117,7 +71,7 @@ void outputFormat(@NonNull TrackType type, @NonNull MediaFormat format) { */ @NonNull TrackStatus status(@NonNull TrackType type) { - return status.get(type); + return status.require(type); } /** @@ -126,7 +80,7 @@ TrackStatus status(@NonNull TrackType type) { * @param status status */ void status(@NonNull TrackType type, @NonNull TrackStatus status) { - this.status.put(type, status); + this.status.set(type, status); } /** @@ -141,20 +95,20 @@ boolean has(@NonNull TrackType type) { @NonNull static Tracks create(@NonNull MediaExtractor extractor) { Tracks tracks = new Tracks(); - tracks.index.put(TrackType.VIDEO, -1); - tracks.index.put(TrackType.AUDIO, -1); + tracks.index.set(TrackType.VIDEO, -1); + tracks.index.set(TrackType.AUDIO, -1); int trackCount = extractor.getTrackCount(); for (int i = 0; i < trackCount; i++) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (!tracks.has(TrackType.VIDEO) && mime.startsWith("video/")) { - tracks.index.put(TrackType.VIDEO, i); - tracks.mimeType.put(TrackType.VIDEO, mime); - tracks.format.put(TrackType.VIDEO, format); + tracks.index.set(TrackType.VIDEO, i); + tracks.mimeType.set(TrackType.VIDEO, mime); + tracks.format.set(TrackType.VIDEO, format); } else if (!tracks.has(TrackType.AUDIO) && mime.startsWith("audio/")) { - tracks.index.put(TrackType.AUDIO, i); - tracks.mimeType.put(TrackType.AUDIO, mime); - tracks.format.put(TrackType.AUDIO, format); + tracks.index.set(TrackType.AUDIO, i); + tracks.mimeType.set(TrackType.AUDIO, mime); + tracks.format.set(TrackType.AUDIO, format); } if (tracks.has(TrackType.VIDEO) && tracks.has(TrackType.AUDIO)) { break; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java index 212d909f..3bcff5cd 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java @@ -18,7 +18,6 @@ import android.media.MediaExtractor; import android.media.MediaFormat; import android.media.MediaMetadataRetriever; -import android.media.MediaMuxer; import android.os.Build; import com.otaliastudios.transcoder.TranscoderOptions; @@ -66,7 +65,7 @@ public interface ProgressCallback { private DataSource mDataSource; private DataSink mDataSink; - private Map mTranscoders = new HashMap<>(); + private TrackTypeMap mTranscoders = new TrackTypeMap<>(); private Tracks mTracks; private MediaExtractor mExtractor; private volatile double mProgress; @@ -114,15 +113,13 @@ public void transcode(@NonNull TranscoderOptions options) throws IOException, In mDataSource.apply(mExtractor); mDataSink = new MediaMuxerDataSink(options.getOutputPath()); setUpMetadata(options); - setupTrackTranscoders(options); + setUpTrackTranscoders(options); runPipelines(); mDataSink.stop(); } finally { try { - TrackTranscoder videoTranscoder = mTranscoders.get(TrackType.VIDEO); - if (videoTranscoder != null) videoTranscoder.release(); - TrackTranscoder audioTranscoder = mTranscoders.get(TrackType.AUDIO); - if (audioTranscoder != null) audioTranscoder.release(); + mTranscoders.require(TrackType.VIDEO).release(); + mTranscoders.require(TrackType.AUDIO).release(); mTranscoders.clear(); if (mExtractor != null) { @@ -217,10 +214,10 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, // Just to respect nullability in setUp(). if (outputFormat == null) outputFormat = inputFormat; transcoder.setUp(outputFormat); - mTranscoders.put(type, transcoder); + mTranscoders.set(type, transcoder); } - private void setupTrackTranscoders(@NonNull TranscoderOptions options) { + private void setUpTrackTranscoders(@NonNull TranscoderOptions options) { mTracks = Tracks.create(mExtractor); setUpTrackTranscoder(options, TrackType.VIDEO); setUpTrackTranscoder(options, TrackType.AUDIO); @@ -243,7 +240,6 @@ private void setupTrackTranscoders(@NonNull TranscoderOptions options) { if (audioStatus.isTranscoding()) mExtractor.selectTrack(mTracks.index(TrackType.AUDIO)); } - @SuppressWarnings("ConstantConditions") private void runPipelines() throws InterruptedException { long loopCount = 0; if (mDurationUs <= 0) { @@ -251,8 +247,8 @@ private void runPipelines() throws InterruptedException { mProgress = progress; if (mProgressCallback != null) mProgressCallback.onProgress(progress); // unknown } - TrackTranscoder videoTranscoder = mTranscoders.get(TrackType.VIDEO); - TrackTranscoder audioTranscoder = mTranscoders.get(TrackType.AUDIO); + TrackTranscoder videoTranscoder = mTranscoders.require(TrackType.VIDEO); + TrackTranscoder audioTranscoder = mTranscoders.require(TrackType.AUDIO); while (!(videoTranscoder.isFinished() && audioTranscoder.isFinished())) { if (Thread.interrupted()) { throw new InterruptedException(); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java index 7aec8368..1729d8b3 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java @@ -9,6 +9,7 @@ import com.otaliastudios.transcoder.engine.TrackStatus; import com.otaliastudios.transcoder.engine.TrackType; +import com.otaliastudios.transcoder.engine.TrackTypeMap; import com.otaliastudios.transcoder.internal.Logger; import com.otaliastudios.transcoder.transcode.PassThroughTrackTranscoder; import com.otaliastudios.transcoder.transcode.TrackTranscoder; @@ -17,9 +18,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** @@ -62,9 +61,9 @@ private QueuedSample(@NonNull TrackTranscoder transcoder, private final MediaMuxer mMuxer; private final List mQueue = new ArrayList<>(); private ByteBuffer mQueueBuffer; - private Map mStatus = new HashMap<>(); - private Map mLastFormat = new HashMap<>(); - private Map mMuxerIndex = new HashMap<>(); + private TrackTypeMap mStatus = new TrackTypeMap<>(); + private TrackTypeMap mLastFormat = new TrackTypeMap<>(); + private TrackTypeMap mMuxerIndex = new TrackTypeMap<>(); private final MediaMuxerChecks mMuxerChecks = new MediaMuxerChecks(); public MediaMuxerDataSink(@NonNull String outputFilePath) { @@ -89,30 +88,27 @@ public void setLocation(double latitude, double longitude) { @Override public void setTrackStatus(@NonNull TrackType type, @NonNull TrackStatus status) { - mStatus.put(type, status); + mStatus.set(type, status); } @Override public void setTrackOutputFormat(@NonNull TrackTranscoder transcoder, @NonNull TrackType type, @NonNull MediaFormat format) { - //noinspection ConstantConditions - boolean shouldValidate = mStatus.get(type).isTranscoding() + boolean shouldValidate = mStatus.require(type).isTranscoding() && !(transcoder instanceof PassThroughTrackTranscoder); if (shouldValidate) { mMuxerChecks.checkOutputFormat(type, format); } - mLastFormat.put(type, format); + mLastFormat.set(type, format); startIfNeeded(); } private void startIfNeeded() { if (mMuxerStarted) return; - //noinspection ConstantConditions - boolean isTranscodingVideo = mStatus.get(TrackType.VIDEO).isTranscoding(); - //noinspection ConstantConditions - boolean isTranscodingAudio = mStatus.get(TrackType.AUDIO).isTranscoding(); + boolean isTranscodingVideo = mStatus.require(TrackType.VIDEO).isTranscoding(); + boolean isTranscodingAudio = mStatus.require(TrackType.AUDIO).isTranscoding(); MediaFormat videoOutputFormat = mLastFormat.get(TrackType.VIDEO); MediaFormat audioOutputFormat = mLastFormat.get(TrackType.AUDIO); boolean isVideoReady = videoOutputFormat != null || !isTranscodingVideo; @@ -123,12 +119,12 @@ private void startIfNeeded() { // We will stop buffering data and we will start actually muxing it. if (isTranscodingVideo) { int videoIndex = mMuxer.addTrack(videoOutputFormat); - mMuxerIndex.put(TrackType.VIDEO, videoIndex); + mMuxerIndex.set(TrackType.VIDEO, videoIndex); LOG.v("Added track #" + videoIndex + " with " + videoOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); } if (isTranscodingAudio) { int audioIndex = mMuxer.addTrack(audioOutputFormat); - mMuxerIndex.put(TrackType.AUDIO, audioIndex); + mMuxerIndex.set(TrackType.AUDIO, audioIndex); LOG.v("Added track #" + audioIndex + " with " + audioOutputFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); } mMuxer.start(); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java b/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java index d5853195..e702b447 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java @@ -1,14 +1,11 @@ package com.otaliastudios.transcoder.time; -import android.util.Log; - import androidx.annotation.NonNull; import com.otaliastudios.transcoder.engine.TrackType; +import com.otaliastudios.transcoder.engine.TrackTypeMap; import com.otaliastudios.transcoder.internal.Logger; -import java.util.HashMap; -import java.util.Map; /** * A {@link TimeInterpolator} that modifies the playback speed by the given @@ -21,7 +18,7 @@ public class SpeedTimeInterpolator implements TimeInterpolator { private final static Logger LOG = new Logger(TAG); private double mFactor; - private final Map mTrackData = new HashMap<>(); + private final TrackTypeMap mTrackData = new TrackTypeMap<>(); /** * Creates a new speed interpolator for the given factor. @@ -46,8 +43,8 @@ public float getFactor() { @Override public long interpolate(@NonNull TrackType type, long time) { - if (!mTrackData.containsKey(type)) { - mTrackData.put(type, new TrackData()); + if (!mTrackData.has(type)) { + mTrackData.set(type, new TrackData()); } TrackData data = mTrackData.get(type); //noinspection ConstantConditions From 5e55ba31f1036f9b099bf6264ebddffedccacf21 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 21:52:46 +0200 Subject: [PATCH 07/12] Abstract MediaExtractor into DataSource --- .../otaliastudios/transcoder/Transcoder.java | 6 - .../transcoder/engine/TrackTypeMap.java | 3 +- .../transcoder/engine/Tracks.java | 49 ++---- .../transcoder/engine/TranscoderEngine.java | 74 ++------- .../transcoder/source/AndroidDataSource.java | 157 ++++++++++++++++++ .../transcoder/source/DataSource.java | 41 ++++- .../source/FileDescriptorDataSource.java | 7 +- .../transcoder/source/FilePathDataSource.java | 4 +- .../transcoder/source/UriDataSource.java | 6 +- .../transcode/AudioTrackTranscoder.java | 6 +- .../transcode/BaseTrackTranscoder.java | 48 +++--- .../transcode/PassThroughTrackTranscoder.java | 61 +++---- .../transcode/VideoTrackTranscoder.java | 6 +- 13 files changed, 297 insertions(+), 171 deletions(-) create mode 100644 lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java diff --git a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java index 637b4558..dd647a3f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java @@ -140,12 +140,6 @@ public void onProgress(final double progress) { LOG.i("Transcode canceled.", current); listenerWrapper.onTranscodeCanceled(); - } else if (e instanceof IOException) { - LOG.w("Transcode failed: input source (" + options.getDataSource().toString() + ") not found" - + " or could not open output file ('" + options.getOutputPath() + "') .", e); - listenerWrapper.onTranscodeFailed(e); - throw e; - } else if (e instanceof RuntimeException) { LOG.e("Fatal error while transcoding, this might be invalid format or bug in engine or Android.", e); listenerWrapper.onTranscodeFailed(e); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java index 342367d8..14b0803f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java @@ -16,7 +16,8 @@ public class TrackTypeMap { private Map map = new HashMap<>(); - public void set(@NonNull TrackType type, @NonNull T value) { + public void set(@NonNull TrackType type, @Nullable T value) { + //noinspection ConstantConditions map.put(type, value); } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java index 58a067bb..6b8c8ba6 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java @@ -19,30 +19,22 @@ import android.media.MediaFormat; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.otaliastudios.transcoder.source.DataSource; /** * Contains information about the tracks as read from the {@link MediaExtractor} * metadata, plus other track-specific information that we can store here. */ -@SuppressWarnings("ConstantConditions") class Tracks { private Tracks() { } - private TrackTypeMap index = new TrackTypeMap<>(); private TrackTypeMap mimeType = new TrackTypeMap<>(); private TrackTypeMap format = new TrackTypeMap<>(); private TrackTypeMap status = new TrackTypeMap<>(); - /** - * The index in the input file. - * @param type track type - * @return index - */ - int index(@NonNull TrackType type) { - return index.get(type); - } - /** * The mime type in the input file. * @param type track type @@ -59,9 +51,9 @@ String mimeType(@NonNull TrackType type) { * @param type track type * @return format */ - @NonNull + @Nullable MediaFormat format(@NonNull TrackType type) { - return format.require(type); + return format.get(type); } /** @@ -89,30 +81,21 @@ void status(@NonNull TrackType type, @NonNull TrackStatus status) { * @return true if present */ boolean has(@NonNull TrackType type) { - return index(type) >= 0; + return format(type) != null; } @NonNull - static Tracks create(@NonNull MediaExtractor extractor) { + static Tracks create(@NonNull DataSource source) { Tracks tracks = new Tracks(); - tracks.index.set(TrackType.VIDEO, -1); - tracks.index.set(TrackType.AUDIO, -1); - int trackCount = extractor.getTrackCount(); - for (int i = 0; i < trackCount; i++) { - MediaFormat format = extractor.getTrackFormat(i); - String mime = format.getString(MediaFormat.KEY_MIME); - if (!tracks.has(TrackType.VIDEO) && mime.startsWith("video/")) { - tracks.index.set(TrackType.VIDEO, i); - tracks.mimeType.set(TrackType.VIDEO, mime); - tracks.format.set(TrackType.VIDEO, format); - } else if (!tracks.has(TrackType.AUDIO) && mime.startsWith("audio/")) { - tracks.index.set(TrackType.AUDIO, i); - tracks.mimeType.set(TrackType.AUDIO, mime); - tracks.format.set(TrackType.AUDIO, format); - } - if (tracks.has(TrackType.VIDEO) && tracks.has(TrackType.AUDIO)) { - break; - } + MediaFormat audioFormat = source.getFormat(TrackType.AUDIO); + tracks.format.set(TrackType.AUDIO, audioFormat); + if (audioFormat != null) { + tracks.mimeType.set(TrackType.AUDIO, audioFormat.getString(MediaFormat.KEY_MIME)); + } + MediaFormat videoFormat = source.getFormat(TrackType.VIDEO); + tracks.format.set(TrackType.VIDEO, videoFormat); + if (videoFormat != null) { + tracks.mimeType.set(TrackType.VIDEO, videoFormat.getString(MediaFormat.KEY_MIME)); } return tracks; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java index 3bcff5cd..1ffea045 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java @@ -32,13 +32,8 @@ import com.otaliastudios.transcoder.transcode.PassThroughTrackTranscoder; import com.otaliastudios.transcoder.transcode.TrackTranscoder; import com.otaliastudios.transcoder.transcode.VideoTrackTranscoder; -import com.otaliastudios.transcoder.engine.internal.ISO6709LocationParser; import com.otaliastudios.transcoder.internal.Logger; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -67,7 +62,6 @@ public interface ProgressCallback { private DataSink mDataSink; private TrackTypeMap mTranscoders = new TrackTypeMap<>(); private Tracks mTracks; - private MediaExtractor mExtractor; private volatile double mProgress; private ProgressCallback mProgressCallback; private long mDurationUs; @@ -98,21 +92,19 @@ public double getProgress() { * Performs transcoding. Blocks current thread. * * @param options Transcoding options. - * @throws IOException when input or output file could not be opened. * @throws InvalidOutputFormatException when output format is not supported. * @throws InterruptedException when cancel to transcode * @throws ValidatorException if validator decides transcoding is not needed. */ - public void transcode(@NonNull TranscoderOptions options) throws IOException, InterruptedException { - if (mDataSource == null) { - throw new IllegalStateException("Data source is not set."); - } + public void transcode(@NonNull TranscoderOptions options) throws InterruptedException { try { // NOTE: use single extractor to keep from running out audio track fast. - mExtractor = new MediaExtractor(); - mDataSource.apply(mExtractor); mDataSink = new MediaMuxerDataSink(options.getOutputPath()); - setUpMetadata(options); + mDataSink.setOrientation((mDataSource.getOrientation() + options.getRotation()) % 360); + double[] location = mDataSource.getLocation(); + if (location != null) mDataSink.setLocation(location[0], location[1]); + mDurationUs = mDataSource.getDurationUs(); + LOG.v("Duration (us): " + mDurationUs); setUpTrackTranscoders(options); runPipelines(); mDataSink.stop(); @@ -121,11 +113,6 @@ public void transcode(@NonNull TranscoderOptions options) throws IOException, In mTranscoders.require(TrackType.VIDEO).release(); mTranscoders.require(TrackType.AUDIO).release(); mTranscoders.clear(); - - if (mExtractor != null) { - mExtractor.release(); - mExtractor = null; - } } catch (RuntimeException e) { // Too fatal to make alive the app, because it may leak native resources. //noinspection ThrowFromFinallyBlock @@ -135,37 +122,6 @@ public void transcode(@NonNull TranscoderOptions options) throws IOException, In } } - private void setUpMetadata(@NonNull TranscoderOptions options) { - MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); - mDataSource.apply(mediaMetadataRetriever); - - String rotationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); - int rotation = 0; - try { - rotation = Integer.parseInt(rotationString); - } catch (NumberFormatException ignore) {} - mDataSink.setOrientation((rotation + options.getRotation()) % 360); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - String locationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); - if (locationString != null) { - float[] location = new ISO6709LocationParser().parse(locationString); - if (location != null) { - mDataSink.setLocation(location[0], location[1]); - } else { - LOG.v("Failed to parse the location metadata: " + locationString); - } - } - } - - try { - mDurationUs = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000; - } catch (NumberFormatException e) { - mDurationUs = -1; - } - LOG.v("Duration (us): " + mDurationUs); - } - private void setUpTrackTranscoder(@NonNull TranscoderOptions options, @NonNull TrackType type) { TrackStatus status; @@ -176,7 +132,6 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, transcoder = new NoOpTrackTranscoder(); status = TrackStatus.ABSENT; } else { - int index = mTracks.index(type); TrackStrategy strategy; switch (type) { case VIDEO: strategy = options.getVideoTrackStrategy(); break; @@ -184,17 +139,19 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, default: throw new RuntimeException("Unknown type: " + type); } try { + // We checked has(), so the input format is not null. + //noinspection ConstantConditions outputFormat = strategy.createOutputFormat(inputFormat); if (outputFormat == null) { transcoder = new NoOpTrackTranscoder(); status = TrackStatus.REMOVING; } else if (outputFormat == inputFormat) { - transcoder = new PassThroughTrackTranscoder(mExtractor, index, mDataSink, type, options.getTimeInterpolator()); + transcoder = new PassThroughTrackTranscoder(mDataSource, mDataSink, type, options.getTimeInterpolator()); status = TrackStatus.PASS_THROUGH; } else { switch (type) { - case VIDEO: transcoder = new VideoTrackTranscoder(mExtractor, mDataSink, index, options.getTimeInterpolator()); break; - case AUDIO: transcoder = new AudioTrackTranscoder(mExtractor, mDataSink, index, options.getTimeInterpolator(), options.getAudioStretcher()); break; + case VIDEO: transcoder = new VideoTrackTranscoder(mDataSource, mDataSink, options.getTimeInterpolator()); break; + case AUDIO: transcoder = new AudioTrackTranscoder(mDataSource, mDataSink, options.getTimeInterpolator(), options.getAudioStretcher()); break; default: throw new RuntimeException("Unknown type: " + type); } status = TrackStatus.COMPRESSING; @@ -202,7 +159,7 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, } catch (TrackStrategyException strategyException) { if (strategyException.getType() == TrackStrategyException.TYPE_ALREADY_COMPRESSED) { // Should not abort, because the other track might need compression. Use a pass through. - transcoder = new PassThroughTrackTranscoder(mExtractor, index, mDataSink, type, options.getTimeInterpolator()); + transcoder = new PassThroughTrackTranscoder(mDataSource, mDataSink, type, options.getTimeInterpolator()); status = TrackStatus.PASS_THROUGH; } else { // Abort. throw strategyException; @@ -210,15 +167,16 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, } } mTracks.status(type, status); + mDataSource.setTrackStatus(type, status); mDataSink.setTrackStatus(type, status); // Just to respect nullability in setUp(). - if (outputFormat == null) outputFormat = inputFormat; + if (outputFormat == null) outputFormat = new MediaFormat(); transcoder.setUp(outputFormat); mTranscoders.set(type, transcoder); } private void setUpTrackTranscoders(@NonNull TranscoderOptions options) { - mTracks = Tracks.create(mExtractor); + mTracks = Tracks.create(mDataSource); setUpTrackTranscoder(options, TrackType.VIDEO); setUpTrackTranscoder(options, TrackType.AUDIO); @@ -236,8 +194,6 @@ private void setUpTrackTranscoders(@NonNull TranscoderOptions options) { && !ignoreValidatorResult) { throw new ValidatorException("Validator returned false."); } - if (videoStatus.isTranscoding()) mExtractor.selectTrack(mTracks.index(TrackType.VIDEO)); - if (audioStatus.isTranscoding()) mExtractor.selectTrack(mTracks.index(TrackType.AUDIO)); } private void runPipelines() throws InterruptedException { diff --git a/lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java b/lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java new file mode 100644 index 00000000..ed3c9546 --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java @@ -0,0 +1,157 @@ +package com.otaliastudios.transcoder.source; + +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.media.MediaMetadataRetriever; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.otaliastudios.transcoder.engine.TrackStatus; +import com.otaliastudios.transcoder.engine.TrackType; +import com.otaliastudios.transcoder.engine.TrackTypeMap; +import com.otaliastudios.transcoder.engine.internal.ISO6709LocationParser; +import com.otaliastudios.transcoder.internal.Logger; + +import java.io.IOException; + +/** + * A DataSource implementation that uses Android's Media APIs. + */ +public abstract class AndroidDataSource implements DataSource { + + private final static String TAG = AndroidDataSource.class.getSimpleName(); + private final static Logger LOG = new Logger(TAG); + + private final MediaMetadataRetriever mMetadata = new MediaMetadataRetriever(); + private final MediaExtractor mExtractor = new MediaExtractor(); + private boolean mMetadataApplied; + private boolean mExtractorApplied; + private final TrackTypeMap mFormats = new TrackTypeMap<>(); + private final TrackTypeMap mIndex = new TrackTypeMap<>(); + + @SuppressWarnings("WeakerAccess") + protected AndroidDataSource() { } + + private void ensureMetadata() { + if (!mMetadataApplied) { + mMetadataApplied = true; + apply(mMetadata); + } + } + + private void ensureExtractor() { + if (!mExtractorApplied) { + mExtractorApplied = true; + try { + apply(mExtractor); + } catch (IOException e) { + LOG.e("Got IOException while trying to open MediaExtractor.", e); + throw new RuntimeException(e); + } + } + } + + protected abstract void apply(@NonNull MediaExtractor extractor) throws IOException; + + protected abstract void apply(@NonNull MediaMetadataRetriever retriever); + + @Override + public void setTrackStatus(@NonNull TrackType type, @NonNull TrackStatus status) { + if (status.isTranscoding()) { + mExtractor.selectTrack(mIndex.require(type)); + } + } + + @Override + public boolean isDrained() { + ensureExtractor(); + return mExtractor.getSampleTrackIndex() < 0; + } + + @Override + public boolean canRead(@NonNull TrackType type) { + ensureExtractor(); + return mExtractor.getSampleTrackIndex() == mIndex.require(type); + } + + @Override + public void read(@NonNull Chunk chunk) { + ensureExtractor(); + chunk.bytes = mExtractor.readSampleData(chunk.buffer, 0); + chunk.isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0; + chunk.timestampUs = mExtractor.getSampleTime(); + mExtractor.advance(); + } + + @Nullable + @Override + public double[] getLocation() { + ensureMetadata(); + String string = mMetadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); + if (string != null) { + float[] location = new ISO6709LocationParser().parse(string); + if (location != null) { + double[] result = new double[2]; + result[0] = (double) location[0]; + result[1] = (double) location[1]; + return result; + } + } + return null; + } + + @Override + public int getOrientation() { + ensureMetadata(); + String string = mMetadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + try { + return Integer.parseInt(string); + } catch (NumberFormatException ignore) { + return 0; + } + } + + @Override + public long getDurationUs() { + ensureMetadata(); + try { + return Long.parseLong(mMetadata + .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000; + } catch (NumberFormatException e) { + return -1; + } + } + + @Nullable + @Override + public MediaFormat getFormat(@NonNull TrackType type) { + if (mFormats.has(type)) return mFormats.get(type); + ensureExtractor(); + int trackCount = mExtractor.getTrackCount(); + MediaFormat format = null; + for (int i = 0; i < trackCount; i++) { + format = mExtractor.getTrackFormat(i); + String mime = format.getString(MediaFormat.KEY_MIME); + if (type == TrackType.VIDEO && mime.startsWith("video/")) { + mIndex.set(TrackType.VIDEO, i); + break; + } + if (type == TrackType.AUDIO && mime.startsWith("audio/")) { + mIndex.set(TrackType.AUDIO, i); + break; + } + } + mFormats.set(type, format); + return format; + } + + @Override + public void release() { + try { + mExtractor.release(); + } catch (Exception e) { + LOG.w("Could not release extractor:", e); + } + } +} diff --git a/lib/src/main/java/com/otaliastudios/transcoder/source/DataSource.java b/lib/src/main/java/com/otaliastudios/transcoder/source/DataSource.java index 7112eb83..158765ad 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/source/DataSource.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/source/DataSource.java @@ -1,20 +1,57 @@ package com.otaliastudios.transcoder.source; import android.media.MediaExtractor; +import android.media.MediaFormat; import android.media.MediaMetadataRetriever; +import android.util.Pair; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.otaliastudios.transcoder.engine.TrackStatus; +import com.otaliastudios.transcoder.engine.TrackType; import java.io.IOException; +import java.nio.ByteBuffer; /** * Represents the source of input data. */ public interface DataSource { - void apply(@NonNull MediaExtractor extractor) throws IOException; + /** + * Called before starting to set the status for the given + * track. The source object can check if the track is transcoding + * using {@link TrackStatus#isTranscoding()}. + * + * @param type track type + * @param status status + */ + void setTrackStatus(@NonNull TrackType type, @NonNull TrackStatus status); + + @Nullable + MediaFormat getFormat(@NonNull TrackType type); + + int getOrientation(); + + @Nullable + double[] getLocation(); - void apply(@NonNull MediaMetadataRetriever retriever); + long getDurationUs(); + + boolean isDrained(); + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + boolean canRead(@NonNull TrackType type); + + void read(@NonNull DataSource.Chunk chunk); void release(); + + class Chunk { + public ByteBuffer buffer; + public boolean isKeyFrame; + public long timestampUs; + public int bytes; + } } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/source/FileDescriptorDataSource.java b/lib/src/main/java/com/otaliastudios/transcoder/source/FileDescriptorDataSource.java index 0b72495a..565c196d 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/source/FileDescriptorDataSource.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/source/FileDescriptorDataSource.java @@ -11,12 +11,13 @@ /** * A {@link DataSource} backed by a file descriptor. */ -public class FileDescriptorDataSource implements DataSource { +public class FileDescriptorDataSource extends AndroidDataSource { @NonNull private FileDescriptor descriptor; public FileDescriptorDataSource(@NonNull FileDescriptor descriptor) { + super(); this.descriptor = descriptor; } @@ -29,8 +30,4 @@ public void apply(@NonNull MediaExtractor extractor) throws IOException { public void apply(@NonNull MediaMetadataRetriever retriever) { retriever.setDataSource(descriptor); } - - @Override - public void release() { - } } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/source/FilePathDataSource.java b/lib/src/main/java/com/otaliastudios/transcoder/source/FilePathDataSource.java index b3e3dbc6..faefe3d0 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/source/FilePathDataSource.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/source/FilePathDataSource.java @@ -15,7 +15,7 @@ /** * A {@link DataSource} backed by a file absolute path. */ -public class FilePathDataSource implements DataSource { +public class FilePathDataSource extends AndroidDataSource { private static final String TAG = "FilePathDataSource"; private static final Logger LOG = new Logger(TAG); @@ -24,6 +24,7 @@ public class FilePathDataSource implements DataSource { @Nullable private FileInputStream stream; public FilePathDataSource(@NonNull String path) { + super(); FileDescriptor fileDescriptor; try { stream = new FileInputStream(path); @@ -47,6 +48,7 @@ public void apply(@NonNull MediaMetadataRetriever retriever) { @Override public void release() { + super.release(); descriptor.release(); if (stream != null) { try { diff --git a/lib/src/main/java/com/otaliastudios/transcoder/source/UriDataSource.java b/lib/src/main/java/com/otaliastudios/transcoder/source/UriDataSource.java index 2d9f081e..7e5bded7 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/source/UriDataSource.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/source/UriDataSource.java @@ -13,12 +13,13 @@ * A {@link DataSource} backed by an Uri, possibly * a content:// uri. */ -public class UriDataSource implements DataSource { +public class UriDataSource extends AndroidDataSource { @NonNull private Context context; @NonNull private Uri uri; public UriDataSource(@NonNull Context context, @NonNull Uri uri) { + super(); this.context = context.getApplicationContext(); this.uri = uri; } @@ -32,7 +33,4 @@ public void apply(@NonNull MediaExtractor extractor) throws IOException { public void apply(@NonNull MediaMetadataRetriever retriever) { retriever.setDataSource(context, uri); } - - @Override - public void release() { } } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java index 0ccd5fd4..914c3b3c 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/AudioTrackTranscoder.java @@ -9,6 +9,7 @@ import com.otaliastudios.transcoder.engine.TrackType; import com.otaliastudios.transcoder.internal.MediaCodecBuffers; import com.otaliastudios.transcoder.sink.DataSink; +import com.otaliastudios.transcoder.source.DataSource; import com.otaliastudios.transcoder.stretch.AudioStretcher; import com.otaliastudios.transcoder.time.TimeInterpolator; import com.otaliastudios.transcoder.transcode.internal.AudioEngine; @@ -23,12 +24,11 @@ public class AudioTrackTranscoder extends BaseTrackTranscoder { private MediaCodec mEncoder; // to create the channel private MediaFormat mEncoderOutputFormat; // to create the channel - public AudioTrackTranscoder(@NonNull MediaExtractor extractor, + public AudioTrackTranscoder(@NonNull DataSource dataSource, @NonNull DataSink dataSink, - int trackIndex, @NonNull TimeInterpolator timeInterpolator, @NonNull AudioStretcher audioStretcher) { - super(extractor, dataSink, TrackType.AUDIO, trackIndex); + super(dataSource, dataSink, TrackType.AUDIO); mTimeInterpolator = timeInterpolator; mAudioStretcher = audioStretcher; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java index 3f890c3d..1cf5688f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/BaseTrackTranscoder.java @@ -10,6 +10,7 @@ import com.otaliastudios.transcoder.engine.TrackType; import com.otaliastudios.transcoder.internal.MediaCodecBuffers; import com.otaliastudios.transcoder.sink.DataSink; +import com.otaliastudios.transcoder.source.DataSource; import java.io.IOException; import java.nio.ByteBuffer; @@ -24,8 +25,8 @@ public abstract class BaseTrackTranscoder implements TrackTranscoder { private static final int DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY = 1; private static final int DRAIN_STATE_CONSUMED = 2; - private final MediaExtractor mExtractor; - private final int mTrackIndex; + private final DataSource mDataSource; + private final DataSource.Chunk mDataChunk; private final DataSink mDataSink; private final TrackType mTrackType; @@ -45,19 +46,17 @@ public abstract class BaseTrackTranscoder implements TrackTranscoder { private boolean mIsExtractorEOS; @SuppressWarnings("WeakerAccess") - protected BaseTrackTranscoder(@NonNull MediaExtractor extractor, + protected BaseTrackTranscoder(@NonNull DataSource dataSource, @NonNull DataSink dataSink, - @NonNull TrackType trackType, - int trackIndex) { - mExtractor = extractor; - mTrackIndex = trackIndex; + @NonNull TrackType trackType) { + mDataSource = dataSource; mDataSink = dataSink; mTrackType = trackType; + mDataChunk = new DataSource.Chunk(); } @Override public final void setUp(@NonNull MediaFormat desiredOutputFormat) { - mExtractor.selectTrack(mTrackIndex); try { mEncoder = MediaCodec.createEncoderByType(desiredOutputFormat.getString(MediaFormat.KEY_MIME)); } catch (IOException e) { @@ -66,7 +65,10 @@ public final void setUp(@NonNull MediaFormat desiredOutputFormat) { onConfigureEncoder(desiredOutputFormat, mEncoder); onStartEncoder(desiredOutputFormat, mEncoder); - final MediaFormat inputFormat = mExtractor.getTrackFormat(mTrackIndex); + final MediaFormat inputFormat = mDataSource.getFormat(mTrackType); + if (inputFormat == null) { + throw new IllegalArgumentException("Input format is null!"); + } try { mDecoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME)); } catch (IOException e) { @@ -201,24 +203,32 @@ protected void onEncoderOutputFormatChanged(@NonNull MediaCodec encoder, @NonNul @SuppressWarnings("SameParameterValue") private int feedDecoder(long timeoutUs) { - if (mIsExtractorEOS) return DRAIN_STATE_NONE; - int trackIndex = mExtractor.getSampleTrackIndex(); - if (trackIndex >= 0 && trackIndex != mTrackIndex) { + if (mIsExtractorEOS) { return DRAIN_STATE_NONE; } - final int result = mDecoder.dequeueInputBuffer(timeoutUs); - if (result < 0) return DRAIN_STATE_NONE; - if (trackIndex < 0) { + if (mDataSource.isDrained()) { + int result = mDecoder.dequeueInputBuffer(timeoutUs); + if (result < 0) return DRAIN_STATE_NONE; mIsExtractorEOS = true; mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); return DRAIN_STATE_NONE; } - final int sampleSize = mExtractor.readSampleData(mDecoderBuffers.getInputBuffer(result), 0); - final boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0; - mDecoder.queueInputBuffer(result, 0, sampleSize, mExtractor.getSampleTime(), isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0); - mExtractor.advance(); + if (!mDataSource.canRead(mTrackType)) { + return DRAIN_STATE_NONE; + } + + final int result = mDecoder.dequeueInputBuffer(timeoutUs); + if (result < 0) return DRAIN_STATE_NONE; + + mDataChunk.buffer = mDecoderBuffers.getInputBuffer(result); + mDataSource.read(mDataChunk); + mDecoder.queueInputBuffer(result, + 0, + mDataChunk.bytes, + mDataChunk.timestampUs, + mDataChunk.isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0); return DRAIN_STATE_CONSUMED; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java index db76ae91..1320393f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/PassThroughTrackTranscoder.java @@ -15,7 +15,6 @@ */ package com.otaliastudios.transcoder.transcode; -import android.annotation.SuppressLint; import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; @@ -24,49 +23,45 @@ import com.otaliastudios.transcoder.engine.TrackType; import com.otaliastudios.transcoder.sink.DataSink; +import com.otaliastudios.transcoder.source.DataSource; import com.otaliastudios.transcoder.time.TimeInterpolator; import java.nio.ByteBuffer; import java.nio.ByteOrder; public class PassThroughTrackTranscoder implements TrackTranscoder { - private final MediaExtractor mExtractor; - private final int mTrackIndex; + + private final DataSource mDataSource; private final DataSink mDataSink; + private final DataSource.Chunk mDataChunk; private final TrackType mTrackType; private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); - private int mBufferSize; - private ByteBuffer mBuffer; private boolean mIsEOS; - private long mLastPresentationTime; - private final MediaFormat mOutputFormat; private boolean mOutputFormatSet = false; - private TimeInterpolator mTimeInterpolator; - public PassThroughTrackTranscoder(@NonNull MediaExtractor extractor, - int trackIndex, + public PassThroughTrackTranscoder(@NonNull DataSource dataSource, @NonNull DataSink dataSink, @NonNull TrackType trackType, @NonNull TimeInterpolator timeInterpolator) { - mExtractor = extractor; - mTrackIndex = trackIndex; + mDataSource = dataSource; mDataSink = dataSink; mTrackType = trackType; - - mOutputFormat = mExtractor.getTrackFormat(mTrackIndex); - mBufferSize = mOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); - mBuffer = ByteBuffer.allocateDirect(mBufferSize).order(ByteOrder.nativeOrder()); - + mOutputFormat = dataSource.getFormat(trackType); + if (mOutputFormat == null) { + throw new IllegalArgumentException("Output format is null!"); + } + int bufferSize = mOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); + mDataChunk = new DataSource.Chunk(); + mDataChunk.buffer = ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.nativeOrder()); mTimeInterpolator = timeInterpolator; } @Override public void setUp(@NonNull MediaFormat desiredOutputFormat) { } - @SuppressLint("Assert") @Override public boolean transcode() { if (mIsEOS) return false; @@ -74,28 +69,24 @@ public boolean transcode() { mDataSink.setTrackOutputFormat(this, mTrackType, mOutputFormat); mOutputFormatSet = true; } - int trackIndex = mExtractor.getSampleTrackIndex(); - if (trackIndex < 0) { - mBuffer.clear(); + if (mDataSource.isDrained()) { + mDataChunk.buffer.clear(); mBufferInfo.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); - mDataSink.write(this, mTrackType, mBuffer, mBufferInfo); + mDataSink.write(this, mTrackType, mDataChunk.buffer, mBufferInfo); mIsEOS = true; return true; } - if (trackIndex != mTrackIndex) return false; - - mBuffer.clear(); - int sampleSize = mExtractor.readSampleData(mBuffer, 0); - assert sampleSize <= mBufferSize; - boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0; - int flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0; - long realTimestampUs = mExtractor.getSampleTime(); - long timestampUs = mTimeInterpolator.interpolate(mTrackType, realTimestampUs); - mBufferInfo.set(0, sampleSize, timestampUs, flags); - mDataSink.write(this, mTrackType, mBuffer, mBufferInfo); - mLastPresentationTime = realTimestampUs; + if (!mDataSource.canRead(mTrackType)) { + return false; + } - mExtractor.advance(); + mDataChunk.buffer.clear(); + mDataSource.read(mDataChunk); + long timestampUs = mTimeInterpolator.interpolate(mTrackType, mDataChunk.timestampUs); + int flags = mDataChunk.isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0; + mBufferInfo.set(0, mDataChunk.bytes, timestampUs, flags); + mDataSink.write(this, mTrackType, mDataChunk.buffer, mBufferInfo); + mLastPresentationTime = mDataChunk.timestampUs; return true; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java index 4ea0549f..96e0c327 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java @@ -24,6 +24,7 @@ import com.otaliastudios.transcoder.engine.TrackType; import com.otaliastudios.transcoder.internal.MediaCodecBuffers; import com.otaliastudios.transcoder.sink.DataSink; +import com.otaliastudios.transcoder.source.DataSource; import com.otaliastudios.transcoder.time.TimeInterpolator; import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput; import com.otaliastudios.transcoder.transcode.internal.VideoEncoderInput; @@ -47,11 +48,10 @@ public class VideoTrackTranscoder extends BaseTrackTranscoder { private TimeInterpolator mTimeInterpolator; public VideoTrackTranscoder( - @NonNull MediaExtractor extractor, + @NonNull DataSource dataSource, @NonNull DataSink dataSink, - int trackIndex, @NonNull TimeInterpolator timeInterpolator) { - super(extractor, dataSink, TrackType.VIDEO, trackIndex); + super(dataSource, dataSink, TrackType.VIDEO); mTimeInterpolator = timeInterpolator; } From 915adb2be12b532dba926546e03dff6b931a6a69 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 21:59:12 +0200 Subject: [PATCH 08/12] Cleanup --- .../otaliastudios/transcoder/Transcoder.java | 5 +- .../transcoder/engine/Tracks.java | 102 ------------------ .../transcoder/engine/TranscoderEngine.java | 38 +++---- 3 files changed, 14 insertions(+), 131 deletions(-) delete mode 100644 lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java diff --git a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java index dd647a3f..126df88c 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java @@ -23,7 +23,6 @@ import com.otaliastudios.transcoder.validator.Validator; import com.otaliastudios.transcoder.engine.ValidatorException; -import java.io.IOException; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; @@ -113,14 +112,12 @@ public Future transcode(@NonNull final TranscoderOptions options) { @Override public Void call() throws Exception { try { - TranscoderEngine engine = new TranscoderEngine(); - engine.setProgressCallback(new TranscoderEngine.ProgressCallback() { + TranscoderEngine engine = new TranscoderEngine(options.getDataSource(), new TranscoderEngine.ProgressCallback() { @Override public void onProgress(final double progress) { listenerWrapper.onTranscodeProgress(progress); } }); - engine.setDataSource(options.getDataSource()); engine.transcode(options); listenerWrapper.onTranscodeCompleted(SUCCESS_TRANSCODED); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java deleted file mode 100644 index 6b8c8ba6..00000000 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/Tracks.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 com.otaliastudios.transcoder.engine; - -import android.media.MediaExtractor; -import android.media.MediaFormat; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.otaliastudios.transcoder.source.DataSource; - -/** - * Contains information about the tracks as read from the {@link MediaExtractor} - * metadata, plus other track-specific information that we can store here. - */ -class Tracks { - - private Tracks() { } - - private TrackTypeMap mimeType = new TrackTypeMap<>(); - private TrackTypeMap format = new TrackTypeMap<>(); - private TrackTypeMap status = new TrackTypeMap<>(); - - /** - * The mime type in the input file. - * @param type track type - * @return mime type - */ - @SuppressWarnings("unused") - @NonNull - String mimeType(@NonNull TrackType type) { - return mimeType.require(type); - } - - /** - * The format in the input file. - * @param type track type - * @return format - */ - @Nullable - MediaFormat format(@NonNull TrackType type) { - return format.get(type); - } - - /** - * The track status for this track. - * @param type track type - * @return track status - */ - @NonNull - TrackStatus status(@NonNull TrackType type) { - return status.require(type); - } - - /** - * Sets the track status for this track. - * @param type track type - * @param status status - */ - void status(@NonNull TrackType type, @NonNull TrackStatus status) { - this.status.set(type, status); - } - - /** - * Whether the given track is present in the input file. - * @param type track type - * @return true if present - */ - boolean has(@NonNull TrackType type) { - return format(type) != null; - } - - @NonNull - static Tracks create(@NonNull DataSource source) { - Tracks tracks = new Tracks(); - MediaFormat audioFormat = source.getFormat(TrackType.AUDIO); - tracks.format.set(TrackType.AUDIO, audioFormat); - if (audioFormat != null) { - tracks.mimeType.set(TrackType.AUDIO, audioFormat.getString(MediaFormat.KEY_MIME)); - } - MediaFormat videoFormat = source.getFormat(TrackType.VIDEO); - tracks.format.set(TrackType.VIDEO, videoFormat); - if (videoFormat != null) { - tracks.mimeType.set(TrackType.VIDEO, videoFormat.getString(MediaFormat.KEY_MIME)); - } - return tracks; - } -} diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java index 1ffea045..f2968f88 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java @@ -15,10 +15,7 @@ */ package com.otaliastudios.transcoder.engine; -import android.media.MediaExtractor; import android.media.MediaFormat; -import android.media.MediaMetadataRetriever; -import android.os.Build; import com.otaliastudios.transcoder.TranscoderOptions; import com.otaliastudios.transcoder.sink.DataSink; @@ -41,6 +38,7 @@ * Internal engine, do not use this directly. */ public class TranscoderEngine { + private static final String TAG = TranscoderEngine.class.getSimpleName(); private static final Logger LOG = new Logger(TAG); @@ -61,21 +59,13 @@ public interface ProgressCallback { private DataSource mDataSource; private DataSink mDataSink; private TrackTypeMap mTranscoders = new TrackTypeMap<>(); - private Tracks mTracks; + private TrackTypeMap mStatuses = new TrackTypeMap<>(); private volatile double mProgress; private ProgressCallback mProgressCallback; private long mDurationUs; - /** - * Do not use this constructor unless you know what you are doing. - */ - public TranscoderEngine() { } - - public void setDataSource(@NonNull DataSource dataSource) { + public TranscoderEngine(@NonNull DataSource dataSource, @Nullable ProgressCallback progressCallback) { mDataSource = dataSource; - } - - public void setProgressCallback(@Nullable ProgressCallback progressCallback) { mProgressCallback = progressCallback; } @@ -126,9 +116,9 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, @NonNull TrackType type) { TrackStatus status; TrackTranscoder transcoder; - MediaFormat inputFormat = mTracks.format(type); + MediaFormat inputFormat = mDataSource.getFormat(type); MediaFormat outputFormat = null; - if (!mTracks.has(type)) { + if (inputFormat == null) { transcoder = new NoOpTrackTranscoder(); status = TrackStatus.ABSENT; } else { @@ -139,8 +129,6 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, default: throw new RuntimeException("Unknown type: " + type); } try { - // We checked has(), so the input format is not null. - //noinspection ConstantConditions outputFormat = strategy.createOutputFormat(inputFormat); if (outputFormat == null) { transcoder = new NoOpTrackTranscoder(); @@ -166,9 +154,9 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, } } } - mTracks.status(type, status); mDataSource.setTrackStatus(type, status); mDataSink.setTrackStatus(type, status); + mStatuses.set(type, status); // Just to respect nullability in setUp(). if (outputFormat == null) outputFormat = new MediaFormat(); transcoder.setUp(outputFormat); @@ -176,12 +164,12 @@ private void setUpTrackTranscoder(@NonNull TranscoderOptions options, } private void setUpTrackTranscoders(@NonNull TranscoderOptions options) { - mTracks = Tracks.create(mDataSource); setUpTrackTranscoder(options, TrackType.VIDEO); setUpTrackTranscoder(options, TrackType.AUDIO); - TrackStatus videoStatus = mTracks.status(TrackType.VIDEO); - TrackStatus audioStatus = mTracks.status(TrackType.AUDIO); + TrackStatus videoStatus = mStatuses.require(TrackType.VIDEO); + TrackStatus audioStatus = mStatuses.require(TrackType.AUDIO); + //noinspection UnusedAssignment boolean ignoreValidatorResult = false; // If we have to apply some rotation, and the video should be transcoded, @@ -212,8 +200,8 @@ private void runPipelines() throws InterruptedException { boolean stepped = videoTranscoder.transcode() || audioTranscoder.transcode(); loopCount++; if (mDurationUs > 0 && loopCount % PROGRESS_INTERVAL_STEPS == 0) { - double videoProgress = getTranscoderProgress(videoTranscoder, mTracks.status(TrackType.VIDEO)); - double audioProgress = getTranscoderProgress(audioTranscoder, mTracks.status(TrackType.AUDIO)); + double videoProgress = getTranscoderProgress(videoTranscoder, mStatuses.require(TrackType.VIDEO)); + double audioProgress = getTranscoderProgress(audioTranscoder, mStatuses.require(TrackType.AUDIO)); LOG.i("progress - video:" + videoProgress + " audio:" + audioProgress); double progress = (videoProgress + audioProgress) / getTranscodersCount(); mProgress = progress; @@ -233,8 +221,8 @@ private double getTranscoderProgress(@NonNull TrackTranscoder transcoder, @NonNu private int getTranscodersCount() { int count = 0; - if (mTracks.status(TrackType.AUDIO).isTranscoding()) count++; - if (mTracks.status(TrackType.VIDEO).isTranscoding()) count++; + if (mStatuses.require(TrackType.AUDIO).isTranscoding()) count++; + if (mStatuses.require(TrackType.VIDEO).isTranscoding()) count++; return (count > 0) ? count : 1; } } From c17c9809a5593600734716b1f3c67f4b0d60851d Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 21:59:48 +0200 Subject: [PATCH 09/12] Rename TranscoderEngine --- .../main/java/com/otaliastudios/transcoder/Transcoder.java | 4 ++-- .../engine/{TranscoderEngine.java => Engine.java} | 6 +++--- .../transcoder/transcode/VideoTrackTranscoder.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename lib/src/main/java/com/otaliastudios/transcoder/engine/{TranscoderEngine.java => Engine.java} (97%) diff --git a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java index 126df88c..e70c8638 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java @@ -17,7 +17,7 @@ import android.os.Handler; -import com.otaliastudios.transcoder.engine.TranscoderEngine; +import com.otaliastudios.transcoder.engine.Engine; import com.otaliastudios.transcoder.source.DataSource; import com.otaliastudios.transcoder.internal.Logger; import com.otaliastudios.transcoder.validator.Validator; @@ -112,7 +112,7 @@ public Future transcode(@NonNull final TranscoderOptions options) { @Override public Void call() throws Exception { try { - TranscoderEngine engine = new TranscoderEngine(options.getDataSource(), new TranscoderEngine.ProgressCallback() { + Engine engine = new Engine(options.getDataSource(), new Engine.ProgressCallback() { @Override public void onProgress(final double progress) { listenerWrapper.onTranscodeProgress(progress); diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java similarity index 97% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java rename to lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java index f2968f88..97a02411 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TranscoderEngine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java @@ -37,9 +37,9 @@ /** * Internal engine, do not use this directly. */ -public class TranscoderEngine { +public class Engine { - private static final String TAG = TranscoderEngine.class.getSimpleName(); + private static final String TAG = Engine.class.getSimpleName(); private static final Logger LOG = new Logger(TAG); private static final double PROGRESS_UNKNOWN = -1.0; @@ -64,7 +64,7 @@ public interface ProgressCallback { private ProgressCallback mProgressCallback; private long mDurationUs; - public TranscoderEngine(@NonNull DataSource dataSource, @Nullable ProgressCallback progressCallback) { + public Engine(@NonNull DataSource dataSource, @Nullable ProgressCallback progressCallback) { mDataSource = dataSource; mProgressCallback = progressCallback; } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java index 96e0c327..8192abff 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java @@ -94,7 +94,7 @@ protected void onCodecsStarted(@NonNull MediaFormat inputFormat, @NonNull MediaF scaleY = outputRatio / inputRatio; } // I don't think we should consider rotation and flip these - we operate on non-rotated - // surfaces and pass the input rotation metadata to the output muxer, see TranscoderEngine.setupMetadata. + // surfaces and pass the input rotation metadata to the output muxer, see Engine.setupMetadata. mDecoderOutputSurface.setScale(scaleX, scaleY); } From cd085caa0a3bfac752a177a17494e1fa3cf3858b Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 22:02:01 +0200 Subject: [PATCH 10/12] Change packages --- .../engine/internal/ISO6709LocationParserTest.java | 2 +- .../main/java/com/otaliastudios/transcoder/Transcoder.java | 2 +- .../java/com/otaliastudios/transcoder/engine/Engine.java | 2 ++ .../transcoder/{engine => }/internal/AvcCsdUtils.java | 2 +- .../transcoder/{engine => }/internal/AvcSpsUtils.java | 2 +- .../{engine => }/internal/ISO6709LocationParser.java | 2 +- .../transcoder/{engine => internal}/TrackTypeMap.java | 4 +++- .../transcoder/{engine => internal}/ValidatorException.java | 4 ++-- .../com/otaliastudios/transcoder/sink/MediaMuxerChecks.java | 4 ++-- .../otaliastudios/transcoder/sink/MediaMuxerDataSink.java | 5 ++--- .../otaliastudios/transcoder/source/AndroidDataSource.java | 4 ++-- .../otaliastudios/transcoder/time/SpeedTimeInterpolator.java | 2 +- 12 files changed, 19 insertions(+), 16 deletions(-) rename lib/src/main/java/com/otaliastudios/transcoder/{engine => }/internal/AvcCsdUtils.java (98%) rename lib/src/main/java/com/otaliastudios/transcoder/{engine => }/internal/AvcSpsUtils.java (97%) rename lib/src/main/java/com/otaliastudios/transcoder/{engine => }/internal/ISO6709LocationParser.java (96%) rename lib/src/main/java/com/otaliastudios/transcoder/{engine => internal}/TrackTypeMap.java (90%) rename lib/src/main/java/com/otaliastudios/transcoder/{engine => internal}/ValidatorException.java (88%) diff --git a/lib/src/androidTest/java/com/otaliastudios/transcoder/engine/internal/ISO6709LocationParserTest.java b/lib/src/androidTest/java/com/otaliastudios/transcoder/engine/internal/ISO6709LocationParserTest.java index 6d75b9c0..f8abf949 100644 --- a/lib/src/androidTest/java/com/otaliastudios/transcoder/engine/internal/ISO6709LocationParserTest.java +++ b/lib/src/androidTest/java/com/otaliastudios/transcoder/engine/internal/ISO6709LocationParserTest.java @@ -3,7 +3,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.otaliastudios.transcoder.engine.internal.ISO6709LocationParser; +import com.otaliastudios.transcoder.internal.ISO6709LocationParser; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java index e70c8638..55b7452d 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/Transcoder.java @@ -21,7 +21,7 @@ import com.otaliastudios.transcoder.source.DataSource; import com.otaliastudios.transcoder.internal.Logger; import com.otaliastudios.transcoder.validator.Validator; -import com.otaliastudios.transcoder.engine.ValidatorException; +import com.otaliastudios.transcoder.internal.ValidatorException; import java.util.concurrent.Callable; import java.util.concurrent.Future; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java index 97a02411..ddff6e0a 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java @@ -18,6 +18,8 @@ import android.media.MediaFormat; import com.otaliastudios.transcoder.TranscoderOptions; +import com.otaliastudios.transcoder.internal.TrackTypeMap; +import com.otaliastudios.transcoder.internal.ValidatorException; import com.otaliastudios.transcoder.sink.DataSink; import com.otaliastudios.transcoder.sink.InvalidOutputFormatException; import com.otaliastudios.transcoder.sink.MediaMuxerDataSink; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/internal/AvcCsdUtils.java b/lib/src/main/java/com/otaliastudios/transcoder/internal/AvcCsdUtils.java similarity index 98% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/internal/AvcCsdUtils.java rename to lib/src/main/java/com/otaliastudios/transcoder/internal/AvcCsdUtils.java index 59550612..616fccba 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/internal/AvcCsdUtils.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/internal/AvcCsdUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.otaliastudios.transcoder.engine.internal; +package com.otaliastudios.transcoder.internal; import android.media.MediaFormat; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/internal/AvcSpsUtils.java b/lib/src/main/java/com/otaliastudios/transcoder/internal/AvcSpsUtils.java similarity index 97% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/internal/AvcSpsUtils.java rename to lib/src/main/java/com/otaliastudios/transcoder/internal/AvcSpsUtils.java index e039e3cb..171f0a1f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/internal/AvcSpsUtils.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/internal/AvcSpsUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.otaliastudios.transcoder.engine.internal; +package com.otaliastudios.transcoder.internal; import androidx.annotation.NonNull; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/internal/ISO6709LocationParser.java b/lib/src/main/java/com/otaliastudios/transcoder/internal/ISO6709LocationParser.java similarity index 96% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/internal/ISO6709LocationParser.java rename to lib/src/main/java/com/otaliastudios/transcoder/internal/ISO6709LocationParser.java index 198443bb..91ded25f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/internal/ISO6709LocationParser.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/internal/ISO6709LocationParser.java @@ -1,4 +1,4 @@ -package com.otaliastudios.transcoder.engine.internal; +package com.otaliastudios.transcoder.internal; import androidx.annotation.Nullable; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java b/lib/src/main/java/com/otaliastudios/transcoder/internal/TrackTypeMap.java similarity index 90% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java rename to lib/src/main/java/com/otaliastudios/transcoder/internal/TrackTypeMap.java index 14b0803f..23296702 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/TrackTypeMap.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/internal/TrackTypeMap.java @@ -1,8 +1,10 @@ -package com.otaliastudios.transcoder.engine; +package com.otaliastudios.transcoder.internal; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.otaliastudios.transcoder.engine.TrackType; + import java.util.HashMap; import java.util.Map; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/ValidatorException.java b/lib/src/main/java/com/otaliastudios/transcoder/internal/ValidatorException.java similarity index 88% rename from lib/src/main/java/com/otaliastudios/transcoder/engine/ValidatorException.java rename to lib/src/main/java/com/otaliastudios/transcoder/internal/ValidatorException.java index 04e551eb..d12813df 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/ValidatorException.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/internal/ValidatorException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.otaliastudios.transcoder.engine; +package com.otaliastudios.transcoder.internal; import com.otaliastudios.transcoder.validator.Validator; @@ -24,7 +24,7 @@ * returns false. Not to be used. */ public class ValidatorException extends RuntimeException { - ValidatorException(@Nullable String detailMessage) { + public ValidatorException(@Nullable String detailMessage) { super(detailMessage); } } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerChecks.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerChecks.java index 06813d4b..39b60d12 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerChecks.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerChecks.java @@ -20,8 +20,8 @@ import com.otaliastudios.transcoder.engine.TrackType; import com.otaliastudios.transcoder.internal.Logger; import com.otaliastudios.transcoder.internal.MediaFormatConstants; -import com.otaliastudios.transcoder.engine.internal.AvcCsdUtils; -import com.otaliastudios.transcoder.engine.internal.AvcSpsUtils; +import com.otaliastudios.transcoder.internal.AvcCsdUtils; +import com.otaliastudios.transcoder.internal.AvcSpsUtils; import java.nio.ByteBuffer; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java index 1729d8b3..11abd49f 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java @@ -9,7 +9,7 @@ import com.otaliastudios.transcoder.engine.TrackStatus; import com.otaliastudios.transcoder.engine.TrackType; -import com.otaliastudios.transcoder.engine.TrackTypeMap; +import com.otaliastudios.transcoder.internal.TrackTypeMap; import com.otaliastudios.transcoder.internal.Logger; import com.otaliastudios.transcoder.transcode.PassThroughTrackTranscoder; import com.otaliastudios.transcoder.transcode.TrackTranscoder; @@ -135,8 +135,7 @@ private void startIfNeeded() { @Override public void write(@NonNull TrackTranscoder transcoder, @NonNull TrackType type, @NonNull ByteBuffer byteBuffer, @NonNull MediaCodec.BufferInfo bufferInfo) { if (mMuxerStarted) { - //noinspection ConstantConditions - mMuxer.writeSampleData(mMuxerIndex.get(type), byteBuffer, bufferInfo); + mMuxer.writeSampleData(mMuxerIndex.require(type), byteBuffer, bufferInfo); } else { enqueue(transcoder, type, byteBuffer, bufferInfo); } diff --git a/lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java b/lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java index ed3c9546..3828314e 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/source/AndroidDataSource.java @@ -9,8 +9,8 @@ import com.otaliastudios.transcoder.engine.TrackStatus; import com.otaliastudios.transcoder.engine.TrackType; -import com.otaliastudios.transcoder.engine.TrackTypeMap; -import com.otaliastudios.transcoder.engine.internal.ISO6709LocationParser; +import com.otaliastudios.transcoder.internal.TrackTypeMap; +import com.otaliastudios.transcoder.internal.ISO6709LocationParser; import com.otaliastudios.transcoder.internal.Logger; import java.io.IOException; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java b/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java index e702b447..f82d3f14 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/time/SpeedTimeInterpolator.java @@ -3,7 +3,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.transcoder.engine.TrackType; -import com.otaliastudios.transcoder.engine.TrackTypeMap; +import com.otaliastudios.transcoder.internal.TrackTypeMap; import com.otaliastudios.transcoder.internal.Logger; From 8955e08bf65f60b1a6b1693b3efe78579e1e4e04 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 2 Aug 2019 22:20:46 +0200 Subject: [PATCH 11/12] Simplify logic --- .../com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java index 11abd49f..22700576 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/sink/MediaMuxerDataSink.java @@ -95,8 +95,7 @@ public void setTrackStatus(@NonNull TrackType type, @NonNull TrackStatus status) public void setTrackOutputFormat(@NonNull TrackTranscoder transcoder, @NonNull TrackType type, @NonNull MediaFormat format) { - boolean shouldValidate = mStatus.require(type).isTranscoding() - && !(transcoder instanceof PassThroughTrackTranscoder); + boolean shouldValidate = mStatus.require(type) == TrackStatus.COMPRESSING; if (shouldValidate) { mMuxerChecks.checkOutputFormat(type, format); } From e24988d47facba3b0cc641ea2df867d0397da12e Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sat, 3 Aug 2019 00:48:43 +0200 Subject: [PATCH 12/12] Fix build --- .../main/java/com/otaliastudios/transcoder/engine/Engine.java | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java b/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java index ddff6e0a..5f638a35 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java @@ -86,7 +86,6 @@ public double getProgress() { * @param options Transcoding options. * @throws InvalidOutputFormatException when output format is not supported. * @throws InterruptedException when cancel to transcode - * @throws ValidatorException if validator decides transcoding is not needed. */ public void transcode(@NonNull TranscoderOptions options) throws InterruptedException { try {