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

Scale canvas video strategy #83

Open
wants to merge 2 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 @@ -20,6 +20,10 @@
import com.otaliastudios.transcoder.engine.TrackStatus;
import com.otaliastudios.transcoder.engine.TrackType;
import com.otaliastudios.transcoder.internal.Logger;
import com.otaliastudios.transcoder.scale.DownVideoScaler;
import com.otaliastudios.transcoder.scale.StretchVideoScaler;
import com.otaliastudios.transcoder.scale.UpVideoScaler;
import com.otaliastudios.transcoder.scale.VideoScaler;
import com.otaliastudios.transcoder.sink.DataSink;
import com.otaliastudios.transcoder.sink.DefaultDataSink;
import com.otaliastudios.transcoder.source.DataSource;
Expand Down Expand Up @@ -59,6 +63,7 @@ public class TranscoderActivity extends AppCompatActivity implements
private RadioGroup mVideoFramesGroup;
private RadioGroup mVideoResolutionGroup;
private RadioGroup mVideoAspectGroup;
private RadioGroup mVideoScalingGroup;
private RadioGroup mVideoRotationGroup;
private RadioGroup mSpeedGroup;
private RadioGroup mAudioReplaceGroup;
Expand Down Expand Up @@ -134,6 +139,7 @@ protected void onCreate(Bundle savedInstanceState) {
mVideoFramesGroup = findViewById(R.id.frames);
mVideoResolutionGroup = findViewById(R.id.resolution);
mVideoAspectGroup = findViewById(R.id.aspect);
mVideoScalingGroup = findViewById(R.id.scale);
mVideoRotationGroup = findViewById(R.id.rotation);
mSpeedGroup = findViewById(R.id.speed);
mAudioSampleRateGroup = findViewById(R.id.sampleRate);
Expand All @@ -143,6 +149,7 @@ protected void onCreate(Bundle savedInstanceState) {
mVideoFramesGroup.setOnCheckedChangeListener(mRadioGroupListener);
mVideoResolutionGroup.setOnCheckedChangeListener(mRadioGroupListener);
mVideoAspectGroup.setOnCheckedChangeListener(mRadioGroupListener);
mVideoScalingGroup.setOnCheckedChangeListener(mRadioGroupListener);
mAudioSampleRateGroup.setOnCheckedChangeListener(mRadioGroupListener);
mTrimStartView.addTextChangedListener(mTextListener);
mTrimEndView.addTextChangedListener(mTextListener);
Expand Down Expand Up @@ -296,11 +303,18 @@ private void transcode() {
default: speed = 1F;
}

VideoScaler videoScaler;
switch (mVideoScalingGroup.getCheckedRadioButtonId()) {
case R.id.scale_down: videoScaler = new DownVideoScaler(); break;
case R.id.scale_stretch: videoScaler = new StretchVideoScaler(); break;
default: videoScaler = new UpVideoScaler(); break;
}

// Launch the transcoding operation.
mTranscodeStartTime = SystemClock.uptimeMillis();
setIsTranscoding(true);
DataSink sink = new DefaultDataSink(mTranscodeOutputFile.getAbsolutePath());
TranscoderOptions.Builder builder = Transcoder.into(sink);
TranscoderOptions.Builder builder = Transcoder.into(sink).setVideoScaler(videoScaler);
if (mAudioReplacementUri == null) {
if (mTranscodeInputUri1 != null) {
DataSource source = new UriDataSource(this, mTranscodeInputUri1);
Expand Down
36 changes: 36 additions & 0 deletions demo/src/main/res/layout/activity_transcoder.xml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,42 @@
android:layout_height="wrap_content" />
</RadioGroup>

<!-- VIDEO SCALE METHOD -->
<TextView
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Video Scaling Method" />
<RadioGroup
android:id="@+id/scale"
android:checkedButton="@id/scale_up"
android:orientation="horizontal"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/scale_up"
android:text="Up"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/scale_down"
android:text="down"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/scale_stretch"
android:text="stretch"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RadioGroup>

<!-- VIDEO ROTATION -->
<TextView
android:padding="16dp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.otaliastudios.transcoder.engine.TrackType;
import com.otaliastudios.transcoder.resample.AudioResampler;
import com.otaliastudios.transcoder.resample.DefaultAudioResampler;
import com.otaliastudios.transcoder.scale.UpVideoScaler;
import com.otaliastudios.transcoder.scale.VideoScaler;
import com.otaliastudios.transcoder.sink.DataSink;
import com.otaliastudios.transcoder.sink.DefaultDataSink;
import com.otaliastudios.transcoder.source.DataSource;
Expand Down Expand Up @@ -51,6 +53,7 @@ private TranscoderOptions() {}
private TimeInterpolator timeInterpolator;
private AudioStretcher audioStretcher;
private AudioResampler audioResampler;
private VideoScaler videoScaler;

TranscoderListener listener;
Handler listenerHandler;
Expand Down Expand Up @@ -104,6 +107,11 @@ public AudioResampler getAudioResampler() {
return audioResampler;
}

@NonNull
public VideoScaler getVideoScaler() {
return videoScaler;
}

public static class Builder {
private DataSink dataSink;
private final List<DataSource> audioDataSources = new ArrayList<>();
Expand All @@ -117,6 +125,7 @@ public static class Builder {
private TimeInterpolator timeInterpolator;
private AudioStretcher audioStretcher;
private AudioResampler audioResampler;
private VideoScaler videoScaler;

Builder(@NonNull String outPath) {
this.dataSink = new DefaultDataSink(outPath);
Expand Down Expand Up @@ -199,13 +208,13 @@ public Builder setAudioTrackStrategy(@Nullable TrackStrategy trackStrategy) {
* Sets the video output strategy. If absent, this defaults to the 16:9
* strategy returned by {@link DefaultVideoStrategies#for720x1280()}.
*
* @param trackStrategy the desired strategy
* @param videoTrackStrategy the desired strategy
* @return this for chaining
*/
@NonNull
@SuppressWarnings("unused")
public Builder setVideoTrackStrategy(@Nullable TrackStrategy trackStrategy) {
this.videoTrackStrategy = trackStrategy;
public Builder setVideoTrackStrategy(@Nullable TrackStrategy videoTrackStrategy) {
this.videoTrackStrategy = videoTrackStrategy;
return this;
}

Expand Down Expand Up @@ -318,6 +327,18 @@ public Builder setAudioResampler(@NonNull AudioResampler audioResampler) {
return this;
}

/**
* Set an {@link VideoScaler} to change the resolution of the video frames
* so that they fit the new resolution
*
*/
@NonNull
@SuppressWarnings("unused")
public Builder setVideoScaler(@NonNull VideoScaler videoScaler) {
this.videoScaler = videoScaler;
return this;
}

/**
* Generates muted audio data sources if needed
* @return The list of audio data sources including the muted sources
Expand Down Expand Up @@ -389,6 +410,9 @@ public TranscoderOptions build() {
if (audioResampler == null) {
audioResampler = new DefaultAudioResampler();
}
if (videoScaler == null) {
videoScaler = new UpVideoScaler();
}
TranscoderOptions options = new TranscoderOptions();
options.listener = listener;
options.audioDataSources = buildAudioDataSources();
Expand All @@ -402,6 +426,7 @@ public TranscoderOptions build() {
options.timeInterpolator = timeInterpolator;
options.audioStretcher = audioStretcher;
options.audioResampler = audioResampler;
options.videoScaler = videoScaler;
return options;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ private void openCurrentStep(@NonNull TrackType type, @NonNull TranscoderOptions
case VIDEO:
transcoder = new VideoTrackTranscoder(dataSource, mDataSink,
interpolator,
options.getVideoScaler(),
options.getVideoRotation());
break;
case AUDIO:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.otaliastudios.transcoder.scale;

import androidx.annotation.NonNull;

import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput;

/**
* An {@link VideoScaler} that scale the video down so that no side exceed the new resolution
* Sides that are too small will have black borders
*/
public class DownVideoScaler implements VideoScaler {
@Override
public void scaleOutput(@NonNull VideoDecoderOutput videoDecoderOutput, float scaleX, float scaleY, boolean flipped) {
if (flipped) { // The drawable is not affected by the flip so we need to reverse it
videoDecoderOutput.setDrawableScale(1.0F / scaleX, 1.0F / scaleY);
} else {
videoDecoderOutput.setDrawableScale(1.0F / scaleY, 1.0F / scaleX);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.otaliastudios.transcoder.scale;

import androidx.annotation.NonNull;

import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput;

/**
* An {@link VideoScaler} that strech the video so that they match the video resolution
* at the cost of deforming the images
*/
public class StretchVideoScaler implements VideoScaler {
@Override
public void scaleOutput(@NonNull VideoDecoderOutput videoDecoderOutput, float scaleX, float scaleY, boolean flipped) {
//No scaling will automatically stretch the frames to fill all the drawable space
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.otaliastudios.transcoder.scale;

import androidx.annotation.NonNull;

import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput;

/**
* An {@link VideoScaler} that scale the video up so that it touches all sides
* of the new resolution and exceeding parts will be truncated
*/
public class UpVideoScaler implements VideoScaler {
@Override
public void scaleOutput(@NonNull VideoDecoderOutput videoDecoderOutput, float scaleX, float scaleY, boolean flipped) {
videoDecoderOutput.setScale(scaleX, scaleY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.otaliastudios.transcoder.scale;

import androidx.annotation.NonNull;

import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput;

/**
* Scale the frames when they are not of the expected resolution
*/
public interface VideoScaler {
/**
* Apply the scaling to the video decoder output
*
* It can be done using VideoDecoderOutput.setScale or VideoDecoderOutput.setDrawableScale
*
* @param videoDecoderOutput the video decoder output
* @param scaleX the input width/height
* @param scaleY the output width/height
* @param flipped whether or not the frame was rotated by 90 degrees
*/
void scaleOutput(@NonNull VideoDecoderOutput videoDecoderOutput, float scaleX, float scaleY, boolean flipped);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.otaliastudios.transcoder.strategy.size.Resizer;
import com.otaliastudios.transcoder.internal.Logger;
import com.otaliastudios.transcoder.internal.MediaFormatConstants;
import com.otaliastudios.transcoder.transcode.internal.VideoDecoderOutput;

import androidx.annotation.NonNull;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import android.media.MediaFormat;

import androidx.annotation.NonNull;

import com.otaliastudios.transcoder.engine.TrackStatus;
import com.otaliastudios.transcoder.strategy.size.Resizer;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@
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.Logger;
import com.otaliastudios.transcoder.internal.MediaCodecBuffers;
import com.otaliastudios.transcoder.internal.MediaFormatConstants;
import com.otaliastudios.transcoder.scale.VideoScaler;
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 java.nio.ByteBuffer;
Expand All @@ -46,16 +46,19 @@ public class VideoTrackTranscoder extends BaseTrackTranscoder {
private MediaCodec mEncoder; // Keep this since we want to signal EOS on it.
private VideoFrameDropper mFrameDropper;
private final TimeInterpolator mTimeInterpolator;
private final VideoScaler mVideoScaler;
private final int mSourceRotation;
private final int mExtraRotation;

public VideoTrackTranscoder(
@NonNull DataSource dataSource,
@NonNull DataSink dataSink,
@NonNull TimeInterpolator timeInterpolator,
@NonNull VideoScaler videoScaler,
int rotation) {
super(dataSource, dataSink, TrackType.VIDEO);
mTimeInterpolator = timeInterpolator;
mVideoScaler = videoScaler;
mSourceRotation = dataSource.getOrientation();
mExtraRotation = rotation;
}
Expand Down Expand Up @@ -130,7 +133,7 @@ protected void onCodecsStarted(@NonNull MediaFormat inputFormat, @NonNull MediaF
} else if (inputRatio < outputRatio) { // Input taller. We have a scaleY.
scaleY = outputRatio / inputRatio;
}
mDecoderOutputSurface.setScale(scaleX, scaleY);
mVideoScaler.scaleOutput(mDecoderOutputSurface, scaleX, scaleY, flip);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ public void setScale(float scaleX, float scaleY) {
mScaleY = scaleY;
}

/**
* Scale the canvas along the two axes.
* @param scaleX x scale
* @param scaleY y scale
*/
@SuppressWarnings("unused")
public void setDrawableScale(float scaleX, float scaleY) {
mDrawable.setRect(
-1.0F * scaleX,
-1.0F * scaleY,
1.0F * scaleX,
1.0F * scaleY
);
}

/**
* Sets the desired frame rotation with respect
* to its natural orientation.
Expand Down