Commit f3d76e9e by hschlueter Committed by Oliver Woodman

Keep orientation information during the transformation.

The input rotation is used to rotate the video during decoding, the
video is rotated so that it is in landscape orientation before encoding
and a rotation is added to the output format where necessary so that
the output video has the same orientation as the input.

PiperOrigin-RevId: 415301328
parent 69532deb
...@@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument; ...@@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import android.annotation.SuppressLint;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecCapabilities;
...@@ -133,6 +134,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -133,6 +134,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @return A configured and started decoder wrapper. * @return A configured and started decoder wrapper.
* @throws IOException If the underlying codec cannot be created. * @throws IOException If the underlying codec cannot be created.
*/ */
@SuppressLint("InlinedApi")
public static MediaCodecAdapterWrapper createForVideoDecoding(Format format, Surface surface) public static MediaCodecAdapterWrapper createForVideoDecoding(Format format, Surface surface)
throws IOException { throws IOException {
@Nullable MediaCodecAdapter adapter = null; @Nullable MediaCodecAdapter adapter = null;
...@@ -141,6 +143,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -141,6 +143,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
MediaFormat.createVideoFormat( MediaFormat.createVideoFormat(
checkNotNull(format.sampleMimeType), format.width, format.height); checkNotNull(format.sampleMimeType), format.width, format.height);
MediaFormatUtil.maybeSetInteger( MediaFormatUtil.maybeSetInteger(
mediaFormat, MediaFormat.KEY_ROTATION, format.rotationDegrees);
MediaFormatUtil.maybeSetInteger(
mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData); MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
adapter = adapter =
...@@ -201,7 +205,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -201,7 +205,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param format The {@link Format} (of the output data) used to determine the underlying {@link * @param format The {@link Format} (of the output data) used to determine the underlying {@link
* MediaCodec} and its configuration values. {@link Format#sampleMimeType}, {@link * MediaCodec} and its configuration values. {@link Format#sampleMimeType}, {@link
* Format#width} and {@link Format#height} must be set to those of the desired output video * Format#width} and {@link Format#height} must be set to those of the desired output video
* format. * format. {@link Format#rotationDegrees} should be 0. The video should always be in landscape
* orientation.
* @param additionalEncoderConfig A map of {@link MediaFormat}'s integer settings, where the keys * @param additionalEncoderConfig A map of {@link MediaFormat}'s integer settings, where the keys
* are from {@code MediaFormat.KEY_*} constants. Its values will override those in {@code * are from {@code MediaFormat.KEY_*} constants. Its values will override those in {@code
* format}. * format}.
...@@ -212,6 +217,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -212,6 +217,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Format format, Map<String, Integer> additionalEncoderConfig) throws IOException { Format format, Map<String, Integer> additionalEncoderConfig) throws IOException {
checkArgument(format.width != Format.NO_VALUE); checkArgument(format.width != Format.NO_VALUE);
checkArgument(format.height != Format.NO_VALUE); checkArgument(format.height != Format.NO_VALUE);
checkArgument(format.height < format.width);
checkArgument(format.rotationDegrees == 0);
@Nullable MediaCodecAdapter adapter = null; @Nullable MediaCodecAdapter adapter = null;
try { try {
......
...@@ -37,6 +37,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -37,6 +37,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static final String TAG = "VideoSamplePipeline"; private static final String TAG = "VideoSamplePipeline";
private final int outputRotationDegrees;
private final DecoderInputBuffer decoderInputBuffer; private final DecoderInputBuffer decoderInputBuffer;
private final MediaCodecAdapterWrapper decoder; private final MediaCodecAdapterWrapper decoder;
...@@ -59,6 +60,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -59,6 +60,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
encoderOutputBuffer = encoderOutputBuffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
// TODO(internal b/209781577): Think about which edge length should be set for portrait videos.
int outputWidth = inputFormat.width; int outputWidth = inputFormat.width;
int outputHeight = inputFormat.height; int outputHeight = inputFormat.height;
if (transformation.outputHeight != Format.NO_VALUE if (transformation.outputHeight != Format.NO_VALUE
...@@ -67,12 +69,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -67,12 +69,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputHeight = transformation.outputHeight; outputHeight = transformation.outputHeight;
} }
if (inputFormat.height > inputFormat.width) {
// The encoder may not support encoding in portrait orientation, so the decoded video is
// rotated to landscape orientation and a rotation is added back later to the output format.
outputRotationDegrees = (inputFormat.rotationDegrees + 90) % 360;
int temp = outputWidth;
outputWidth = outputHeight;
outputHeight = temp;
} else {
outputRotationDegrees = inputFormat.rotationDegrees;
}
// The decoder rotates videos to their intended display orientation. The frameEditor rotates
// them back for improved encoder compatibility.
// TODO(internal b/201293185): After fragment shader transformations are implemented, put
// postrotation in a later vertex shader.
transformation.transformationMatrix.postRotate(outputRotationDegrees);
try { try {
encoder = encoder =
MediaCodecAdapterWrapper.createForVideoEncoding( MediaCodecAdapterWrapper.createForVideoEncoding(
new Format.Builder() new Format.Builder()
.setWidth(outputWidth) .setWidth(outputWidth)
.setHeight(outputHeight) .setHeight(outputHeight)
.setRotationDegrees(0)
.setSampleMimeType( .setSampleMimeType(
transformation.videoMimeType != null transformation.videoMimeType != null
? transformation.videoMimeType ? transformation.videoMimeType
...@@ -84,7 +103,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -84,7 +103,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
throw createRendererException( throw createRendererException(
e, rendererIndex, inputFormat, PlaybackException.ERROR_CODE_UNSPECIFIED); e, rendererIndex, inputFormat, PlaybackException.ERROR_CODE_UNSPECIFIED);
} }
if (inputFormat.height != outputHeight || !transformation.transformationMatrix.isIdentity()) { if (inputFormat.height != outputHeight
|| inputFormat.width != outputWidth
|| !transformation.transformationMatrix.isIdentity()) {
frameEditor = frameEditor =
FrameEditor.create( FrameEditor.create(
context, context,
...@@ -150,7 +171,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -150,7 +171,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
@Nullable @Nullable
public Format getOutputFormat() { public Format getOutputFormat() {
return encoder.getOutputFormat(); Format format = encoder.getOutputFormat();
return format == null
? null
: format.buildUpon().setRotationDegrees(outputRotationDegrees).build();
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment