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 f5e70b86..6702624c 100644 --- a/demo/src/main/java/com/otaliastudios/transcoder/demo/TranscoderActivity.java +++ b/demo/src/main/java/com/otaliastudios/transcoder/demo/TranscoderActivity.java @@ -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; @@ -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; @@ -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); @@ -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); @@ -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); diff --git a/demo/src/main/res/layout/activity_transcoder.xml b/demo/src/main/res/layout/activity_transcoder.xml index 8034a75c..87ccda03 100644 --- a/demo/src/main/res/layout/activity_transcoder.xml +++ b/demo/src/main/res/layout/activity_transcoder.xml @@ -205,6 +205,42 @@ android:layout_height="wrap_content" /> + + + + + + + + audioDataSources = new ArrayList<>(); @@ -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); @@ -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; } @@ -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 @@ -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(); @@ -402,6 +426,7 @@ public TranscoderOptions build() { options.timeInterpolator = timeInterpolator; options.audioStretcher = audioStretcher; options.audioResampler = audioResampler; + options.videoScaler = videoScaler; return options; } 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 742fd1cf..18e2e683 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/engine/Engine.java @@ -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: diff --git a/lib/src/main/java/com/otaliastudios/transcoder/scale/DownVideoScaler.java b/lib/src/main/java/com/otaliastudios/transcoder/scale/DownVideoScaler.java new file mode 100644 index 00000000..b239dc23 --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/scale/DownVideoScaler.java @@ -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); + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/otaliastudios/transcoder/scale/StretchVideoScaler.java b/lib/src/main/java/com/otaliastudios/transcoder/scale/StretchVideoScaler.java new file mode 100644 index 00000000..8862f229 --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/scale/StretchVideoScaler.java @@ -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 + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/otaliastudios/transcoder/scale/UpVideoScaler.java b/lib/src/main/java/com/otaliastudios/transcoder/scale/UpVideoScaler.java new file mode 100644 index 00000000..444eab65 --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/scale/UpVideoScaler.java @@ -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); + } +} diff --git a/lib/src/main/java/com/otaliastudios/transcoder/scale/VideoScaler.java b/lib/src/main/java/com/otaliastudios/transcoder/scale/VideoScaler.java new file mode 100644 index 00000000..b012361f --- /dev/null +++ b/lib/src/main/java/com/otaliastudios/transcoder/scale/VideoScaler.java @@ -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); +} 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 bbab7255..5335b3ed 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultVideoStrategy.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/DefaultVideoStrategy.java @@ -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; diff --git a/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategy.java b/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategy.java index fa2df2a4..a2210e51 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategy.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/strategy/TrackStrategy.java @@ -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; /** 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 1f782566..c4e1e573 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/VideoTrackTranscoder.java @@ -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; @@ -46,6 +46,7 @@ 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; @@ -53,9 +54,11 @@ 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; } @@ -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 diff --git a/lib/src/main/java/com/otaliastudios/transcoder/transcode/internal/VideoDecoderOutput.java b/lib/src/main/java/com/otaliastudios/transcoder/transcode/internal/VideoDecoderOutput.java index 7f052561..efa20220 100644 --- a/lib/src/main/java/com/otaliastudios/transcoder/transcode/internal/VideoDecoderOutput.java +++ b/lib/src/main/java/com/otaliastudios/transcoder/transcode/internal/VideoDecoderOutput.java @@ -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.