Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for custom postprocessing #97

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ public void afterTextChanged(Editable s) {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Logger.setLogLevel(Logger.LEVEL_VERBOSE);
Logger.setLogLevel(Logger.LEVEL_ERROR);
setContentView(R.layout.activity_transcoder);

mButtonView = findViewById(R.id.button);
mButtonView.setOnClickListener(v -> {
if (!mIsTranscoding) {
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT)
.setType("video/*")
.setType("audio/*,video/*")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true), REQUEST_CODE_PICK);
} else {
mTranscodeFuture.cancel(true);
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import android.os.Looper;

import com.otaliastudios.transcoder.engine.TrackType;
import com.otaliastudios.transcoder.io_factory.DecoderIOFactory;
import com.otaliastudios.transcoder.io_factory.DefaultDecoderIOFactory;
import com.otaliastudios.transcoder.resample.AudioResampler;
import com.otaliastudios.transcoder.resample.DefaultAudioResampler;
import com.otaliastudios.transcoder.sink.DataSink;
Expand Down Expand Up @@ -44,6 +46,7 @@ public class TranscoderOptions {
private TranscoderOptions() {}

private DataSink dataSink;
private DecoderIOFactory decoderIOFactory;
private List<DataSource> videoDataSources;
private List<DataSource> audioDataSources;
private TrackStrategy audioTrackStrategy;
Expand All @@ -62,6 +65,11 @@ public DataSink getDataSink() {
return dataSink;
}

@NonNull
public DecoderIOFactory getDecoderIOFactory() {
return decoderIOFactory;
}

@NonNull
public List<DataSource> getAudioDataSources() {
return audioDataSources;
Expand Down Expand Up @@ -108,6 +116,7 @@ public AudioResampler getAudioResampler() {

public static class Builder {
private DataSink dataSink;
private DecoderIOFactory decoderIOFactory;
private final List<DataSource> audioDataSources = new ArrayList<>();
private final List<DataSource> videoDataSources = new ArrayList<>();
private TranscoderListener listener;
Expand Down Expand Up @@ -325,6 +334,19 @@ public Builder setAudioResampler(@NonNull AudioResampler audioResampler) {
return this;
}

/**
* Sets an {@link DecoderIOFactory} to provide decoder input and output.
* Can be use to implement custom video postprocessing
* Defaults to {@link DefaultAudioResampler}.
*
* @param decoderIOFactory a decoder io factory
* @return this for chaining
*/
public Builder setDecoderIOFactory(DecoderIOFactory decoderIOFactory) {
this.decoderIOFactory = decoderIOFactory;
return this;
}

/**
* Generates muted audio data sources if needed
* @return The list of audio data sources including the muted sources
Expand Down Expand Up @@ -396,11 +418,16 @@ public TranscoderOptions build() {
if (audioResampler == null) {
audioResampler = new DefaultAudioResampler();
}
if (decoderIOFactory == null) {
decoderIOFactory = new DefaultDecoderIOFactory();
}

TranscoderOptions options = new TranscoderOptions();
options.listener = listener;
options.audioDataSources = buildAudioDataSources();
options.videoDataSources = videoDataSources;
options.dataSink = dataSink;
options.decoderIOFactory = decoderIOFactory;
options.listenerHandler = listenerHandler;
options.audioTrackStrategy = audioTrackStrategy;
options.videoTrackStrategy = videoTrackStrategy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ private void openCurrentStep(@NonNull TrackType type, @NonNull TranscoderOptions
switch (type) {
case VIDEO:
transcoder = new VideoTrackTranscoder(dataSource, mDataSink,
options.getDecoderIOFactory(),
interpolator,
options.getVideoRotation());
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.otaliastudios.transcoder.io_factory;

import android.view.Surface;

import com.otaliastudios.transcoder.transcode.base.VideoDecoderOutputBase;
import com.otaliastudios.transcoder.transcode.base.VideoEncoderInputBase;

import org.jetbrains.annotations.NotNull;

public interface DecoderIOFactory {
@NotNull VideoEncoderInputBase createVideoInput(@NotNull Surface surface);
@NotNull VideoDecoderOutputBase createVideoOutput();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.otaliastudios.transcoder.io_factory;

import android.view.Surface;

import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput;
import com.otaliastudios.transcoder.transcode.internal.VideoEncoderInput;
import com.otaliastudios.transcoder.transcode.base.VideoDecoderOutputBase;
import com.otaliastudios.transcoder.transcode.base.VideoEncoderInputBase;

import org.jetbrains.annotations.NotNull;

public class DefaultDecoderIOFactory implements DecoderIOFactory {
@NotNull
@Override
public VideoEncoderInputBase createVideoInput(@NotNull Surface surface) {
return new VideoEncoderInput(surface);
}

@NotNull
@Override
public VideoDecoderOutputBase createVideoOutput() {
return new VideoDecoderOutput();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ public abstract class DefaultDataSource implements DataSource {
= new TrackTypeMap<>(0L, 0L);
private long mFirstTimestampUs = Long.MIN_VALUE;

private long mCachedDuration = Long.MIN_VALUE;
private void ensureMetadata() {
if (!mMetadataApplied) {
mMetadataApplied = true;
applyRetriever(mMetadata);
mCachedDuration = Long.MIN_VALUE;
}
}

Expand Down Expand Up @@ -163,9 +165,13 @@ public int getOrientation() {
@Override
public long getDurationUs() {
ensureMetadata();
if (mCachedDuration != Long.MIN_VALUE) {
return mCachedDuration;
}
try {
return Long.parseLong(mMetadata
mCachedDuration = Long.parseLong(mMetadata
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000;
return mCachedDuration;
} catch (NumberFormatException e) {
return -1;
}
Expand Down Expand Up @@ -237,5 +243,6 @@ public void rewind() {
} catch (Exception ignore) { }
mMetadata = new MediaMetadataRetriever();
mMetadataApplied = false;
mCachedDuration = Long.MIN_VALUE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ private Options() {}
private int targetFrameRate;
private float targetKeyFrameInterval;
private String targetMimeType;
private boolean allowPassThrough;
}


/**
* Creates a new {@link Builder} with an {@link Resizer}
* using given dimensions.
*
* @param resizer first resizer
* @return a strategy builder
*/
@NonNull
@SuppressWarnings("WeakerAccess")
public static Builder resizer(Resizer resizer) {
return new Builder(resizer);
}

/**
Expand Down Expand Up @@ -123,7 +138,7 @@ public static class Builder {
private long targetBitRate = BITRATE_UNKNOWN;
private float targetKeyFrameInterval = DEFAULT_KEY_FRAME_INTERVAL;
private String targetMimeType = MediaFormatConstants.MIMETYPE_VIDEO_AVC;

private boolean allowPassThrough = true;
@SuppressWarnings("unused")
public Builder() { }

Expand Down Expand Up @@ -189,6 +204,13 @@ public Builder mimeType(@NonNull String mimeType) {
return this;
}

@SuppressWarnings("unused")
@NonNull
public Builder allowPassThrough(boolean allowPassThrough) {
this.allowPassThrough = allowPassThrough;
return this;
}

@NonNull
@SuppressWarnings("WeakerAccess")
public Options options() {
Expand All @@ -198,6 +220,7 @@ public Options options() {
options.targetBitRate = targetBitRate;
options.targetKeyFrameInterval = targetKeyFrameInterval;
options.targetMimeType = targetMimeType;
options.allowPassThrough = allowPassThrough;
return options;
}

Expand Down Expand Up @@ -264,7 +287,7 @@ public TrackStatus createOutputFormat(@NonNull List<MediaFormat> inputFormats,
// or, for example, each part would be copied into output with its own size,
// breaking the muxer.
boolean canPassThrough = inputFormats.size() == 1;
if (canPassThrough && typeDone && sizeDone && frameRateDone && frameIntervalDone) {
if (options.allowPassThrough && canPassThrough && typeDone && sizeDone && frameRateDone && frameIntervalDone) {
LOG.i("Input minSize: " + inSize.getMinor() + ", desired minSize: " + outSize.getMinor() +
"\nInput frameRate: " + inputFrameRate + ", desired frameRate: " + outFrameRate +
"\nInput iFrameInterval: " + inputIFrameInterval + ", desired iFrameInterval: " + options.targetKeyFrameInterval);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.util.Log;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
Expand Down Expand Up @@ -212,8 +213,9 @@ private int feedDecoder(long timeoutUs, boolean forceInputEos) {
}

final int result = mDecoder.dequeueInputBuffer(timeoutUs);
if (result < 0) return DRAIN_STATE_NONE;

if (result < 0) {
return DRAIN_STATE_NONE;
}
mDataChunk.buffer = mDecoderBuffers.getInputBuffer(result);
mDataSource.readTrack(mDataChunk);
mDecoder.queueInputBuffer(result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,23 @@
package com.otaliastudios.transcoder.transcode;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;

import androidx.annotation.NonNull;

import com.otaliastudios.transcoder.engine.TrackType;
import com.otaliastudios.transcoder.internal.MediaCodecBuffers;
import com.otaliastudios.transcoder.io_factory.DecoderIOFactory;
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;
import com.otaliastudios.transcoder.internal.Logger;
import com.otaliastudios.transcoder.internal.MediaFormatConstants;
import com.otaliastudios.transcoder.transcode.internal.VideoFrameDropper;
import com.otaliastudios.transcoder.transcode.base.VideoDecoderOutputBase;
import com.otaliastudios.transcoder.transcode.base.VideoEncoderInputBase;

import org.jetbrains.annotations.NotNull;

import java.nio.ByteBuffer;

Expand All @@ -41,20 +43,24 @@ public class VideoTrackTranscoder extends BaseTrackTranscoder {
@SuppressWarnings("unused")
private static final Logger LOG = new Logger(TAG);

private VideoDecoderOutput mDecoderOutputSurface;
private VideoEncoderInput mEncoderInputSurface;
private VideoDecoderOutputBase mDecoderOutputSurface;
private VideoEncoderInputBase mEncoderInputSurface;
private MediaCodec mEncoder; // Keep this since we want to signal EOS on it.
private VideoFrameDropper mFrameDropper;
private DecoderIOFactory mDecoderIOFactory;
private final TimeInterpolator mTimeInterpolator;
private final int mSourceRotation;
private final int mExtraRotation;


public VideoTrackTranscoder(
@NonNull DataSource dataSource,
@NonNull DataSink dataSink,
@NotNull DecoderIOFactory decoderIOFactory,
@NonNull TimeInterpolator timeInterpolator,
int rotation) {
super(dataSource, dataSink, TrackType.VIDEO);
mDecoderIOFactory = decoderIOFactory;
mTimeInterpolator = timeInterpolator;
mSourceRotation = dataSource.getOrientation();
mExtraRotation = rotation;
Expand All @@ -76,7 +82,7 @@ protected void onConfigureEncoder(@NonNull MediaFormat format, @NonNull MediaCod

@Override
protected void onStartEncoder(@NonNull MediaFormat format, @NonNull MediaCodec encoder) {
mEncoderInputSurface = new VideoEncoderInput(encoder.createInputSurface());
mEncoderInputSurface = mDecoderIOFactory.createVideoInput(encoder.createInputSurface());
super.onStartEncoder(format, encoder);
}

Expand All @@ -98,10 +104,7 @@ protected void onConfigureDecoder(@NonNull MediaFormat format, @NonNull MediaCod
// refer: https://android.googlesource.com/platform/frameworks/av/+blame/lollipop-release/media/libstagefright/Utils.cpp
format.setInteger(MediaFormatConstants.KEY_ROTATION_DEGREES, 0);

// The rotation we should apply is the intrinsic source rotation, plus any extra
// rotation that was set into the TranscoderOptions.
mDecoderOutputSurface = new VideoDecoderOutput();
mDecoderOutputSurface.setRotation((mSourceRotation + mExtraRotation) % 360);
mDecoderOutputSurface = mDecoderIOFactory.createVideoOutput();
decoder.configure(format, mDecoderOutputSurface.getSurface(), null, 0);
}

Expand All @@ -112,25 +115,7 @@ protected void onCodecsStarted(@NonNull MediaFormat inputFormat, @NonNull MediaF
inputFormat.getInteger(MediaFormat.KEY_FRAME_RATE),
outputFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
mEncoder = encoder;

// Cropping support.
// Ignoring any outputFormat KEY_ROTATION (which is applied at playback time), the rotation
// difference between input and output is mSourceRotation + mExtraRotation.
int rotation = (mSourceRotation + mExtraRotation) % 360;
boolean flip = (rotation % 180) != 0;
float inputWidth = inputFormat.getInteger(MediaFormat.KEY_WIDTH);
float inputHeight = inputFormat.getInteger(MediaFormat.KEY_HEIGHT);
float inputRatio = inputWidth / inputHeight;
float outputWidth = flip ? outputFormat.getInteger(MediaFormat.KEY_HEIGHT) : outputFormat.getInteger(MediaFormat.KEY_WIDTH);
float outputHeight = flip ? outputFormat.getInteger(MediaFormat.KEY_WIDTH) : outputFormat.getInteger(MediaFormat.KEY_HEIGHT);
float outputRatio = outputWidth / outputHeight;
float scaleX = 1, scaleY = 1;
if (inputRatio > outputRatio) { // Input wider. We have a scaleX.
scaleX = inputRatio / outputRatio;
} else if (inputRatio < outputRatio) { // Input taller. We have a scaleY.
scaleY = outputRatio / inputRatio;
}
mDecoderOutputSurface.setScale(scaleX, scaleY);
mDecoderOutputSurface.configureWith(inputFormat, outputFormat, mSourceRotation, mExtraRotation);
}

@Override
Expand Down
Loading