From 79a6c806dad638bef5c0e17e23d1a3ffe492d2f4 Mon Sep 17 00:00:00 2001 From: claincly Date: Thu, 17 Mar 2022 18:22:42 +0000 Subject: [PATCH] Add encoding options. PiperOrigin-RevId: 435398814 --- .../android/exoplayer2/transformer/Codec.java | 6 +- .../transformer/DefaultEncoderFactory.java | 270 +++++++++++------- .../transformer/VideoEncoderSettings.java | 208 ++++++++++++++ .../VideoTranscodingSamplePipeline.java | 2 + 4 files changed, 383 insertions(+), 103 deletions(-) create mode 100644 library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java index affc09940c..c2167a2495 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java @@ -95,8 +95,10 @@ public interface Codec { * * @param format The {@link Format} (of the output data) used to determine the underlying * encoder 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. {@link - * Format#rotationDegrees} should be 0. The video should always be in landscape orientation. + * and {@link Format#height} are set to those of the desired output video format. {@link + * Format#rotationDegrees} is 0 and {@link Format#width} {@code >=} {@link Format#height}, + * therefore the video is always in landscape orientation. {@link Format#frameRate} is set + * to the output video's frame rate, if available. * @param allowedMimeTypes The non-empty list of allowed output sample {@link MimeTypes MIME * types}. * @return A {@link Codec} for video encoding. diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java index 8dd9d2d70d..319c1d4d1b 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java @@ -29,7 +29,6 @@ import android.util.Pair; import android.util.Size; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; @@ -38,32 +37,50 @@ import java.util.List; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** A default implementation of {@link Codec.EncoderFactory}. */ +// TODO(b/224949986) Split audio and video encoder factory. public final class DefaultEncoderFactory implements Codec.EncoderFactory { - private static final int DEFAULT_COLOR_FORMAT = - MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; - private static final int DEFAULT_FRAME_RATE = 60; - private static final int DEFAULT_I_FRAME_INTERVAL_SECS = 1; + private static final int DEFAULT_FRAME_RATE = 30; - @Nullable private final EncoderSelector videoEncoderSelector; + private final EncoderSelector videoEncoderSelector; + private final VideoEncoderSettings requestedVideoEncoderSettings; private final boolean enableFallback; /** - * Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, and - * format fallback enabled. - * - *

With format fallback enabled, and when the requested {@link Format} is not supported, {@code - * DefaultEncoderFactory} finds a format that is supported by the device and configures the {@link - * Codec} with it. The fallback process may change the requested {@link Format#sampleMimeType MIME - * type}, resolution, {@link Format#bitrate bitrate}, {@link Format#codecs profile/level}, etc. + * Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, a + * default {@link VideoEncoderSettings}, and with format fallback enabled. */ public DefaultEncoderFactory() { this(EncoderSelector.DEFAULT, /* enableFallback= */ true); } - /** Creates a new instance. */ + /** Creates a new instance using a default {@link VideoEncoderSettings}. */ + public DefaultEncoderFactory(EncoderSelector videoEncoderSelector, boolean enableFallback) { + this(videoEncoderSelector, new VideoEncoderSettings.Builder().build(), enableFallback); + } + + /** + * Creates a new instance. + * + *

Values in {@code requestedVideoEncoderSettings} could be adjusted to improve encoding + * quality and/or reduce failures. Specifically, {@link VideoEncoderSettings#profile} and {@link + * VideoEncoderSettings#level} are ignored for {@link MimeTypes#VIDEO_H264}. Consider implementing + * {@link Codec.EncoderFactory} if such adjustments are unwanted. + * + *

With format fallback enabled, and when the requested {@link Format} is not supported, {@code + * DefaultEncoderFactory} finds a format that is supported by the device and configures the {@link + * Codec} with it. The fallback process may change the requested {@link Format#sampleMimeType MIME + * type}, resolution, {@link Format#bitrate bitrate}, {@link Format#codecs profile/level}, etc. + * + * @param videoEncoderSelector The {@link EncoderSelector}. + * @param requestedVideoEncoderSettings The {@link VideoEncoderSettings}. + * @param enableFallback Whether to enable fallback. + */ public DefaultEncoderFactory( - @Nullable EncoderSelector videoEncoderSelector, boolean enableFallback) { + EncoderSelector videoEncoderSelector, + VideoEncoderSettings requestedVideoEncoderSettings, + boolean enableFallback) { this.videoEncoderSelector = videoEncoderSelector; + this.requestedVideoEncoderSettings = requestedVideoEncoderSettings; this.enableFallback = enableFallback; } @@ -99,6 +116,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { @Override public Codec createForVideoEncoding(Format format, List allowedMimeTypes) throws TransformationException { + if (format.frameRate == Format.NO_VALUE) { + format = format.buildUpon().setFrameRate(DEFAULT_FRAME_RATE).build(); + } checkArgument(format.width != Format.NO_VALUE); checkArgument(format.height != Format.NO_VALUE); // According to interface Javadoc, format.rotationDegrees should be 0. The video should always @@ -110,78 +130,51 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { checkStateNotNull(videoEncoderSelector); @Nullable - Pair encoderAndClosestFormatSupport = + VideoEncoderQueryResult encoderAndClosestFormatSupport = findEncoderWithClosestFormatSupport( - format, videoEncoderSelector, allowedMimeTypes, enableFallback); + format, + requestedVideoEncoderSettings, + videoEncoderSelector, + allowedMimeTypes, + enableFallback); + if (encoderAndClosestFormatSupport == null) { throw createTransformationException(format); } - MediaCodecInfo encoderInfo = encoderAndClosestFormatSupport.first; - format = encoderAndClosestFormatSupport.second; + MediaCodecInfo encoderInfo = encoderAndClosestFormatSupport.encoder; + format = encoderAndClosestFormatSupport.supportedFormat; + VideoEncoderSettings supportedVideoEncoderSettings = + encoderAndClosestFormatSupport.supportedEncoderSettings; + String mimeType = checkNotNull(format.sampleMimeType); MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, format.width, format.height); mediaFormat.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate); - mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.averageBitrate); + mediaFormat.setInteger( + MediaFormat.KEY_BIT_RATE, + supportedVideoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE + ? supportedVideoEncoderSettings.bitrate + : getSuggestedBitrate(format.width, format.height, format.frameRate)); - @Nullable - Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); - if (codecProfileAndLevel != null) { - // The codecProfileAndLevel is supported by the encoder. - mediaFormat.setInteger(MediaFormat.KEY_PROFILE, codecProfileAndLevel.first); - if (SDK_INT >= 23) { - mediaFormat.setInteger(MediaFormat.KEY_LEVEL, codecProfileAndLevel.second); - } + mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, supportedVideoEncoderSettings.bitrateMode); + + if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE + && supportedVideoEncoderSettings.level != VideoEncoderSettings.NO_VALUE + && SDK_INT >= 23) { + // Set profile and level at the same time to maximize compatibility, or the encoder will pick + // the values. + mediaFormat.setInteger(MediaFormat.KEY_PROFILE, supportedVideoEncoderSettings.profile); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedVideoEncoderSettings.level); } - // TODO(b/210593256): Remove overriding profile/level (before API 29) after switching to in-app - // muxing. if (mimeType.equals(MimeTypes.VIDEO_H264)) { - // Applying suggested profile/level settings from - // https://developer.android.com/guide/topics/media/sharing-video#b-frames_and_encoding_profiles - if (Util.SDK_INT >= 29) { - int supportedEncodingLevel = - EncoderUtil.findHighestSupportedEncodingLevel( - encoderInfo, mimeType, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); - if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) { - // Use the highest supported profile and use B-frames. - mediaFormat.setInteger( - MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); - mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel); - mediaFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 1); - } - } else if (Util.SDK_INT >= 26) { - int supportedEncodingLevel = - EncoderUtil.findHighestSupportedEncodingLevel( - encoderInfo, mimeType, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); - if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) { - // Use the highest-supported profile, but disable the generation of B-frames using - // MediaFormat.KEY_LATENCY. This accommodates some limitations in the MediaMuxer in these - // system versions. - mediaFormat.setInteger( - MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh); - mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel); - // TODO(b/210593256): Set KEY_LATENCY to 2 to enable B-frame production after switching to - // in-app muxing. - mediaFormat.setInteger(MediaFormat.KEY_LATENCY, 1); - } - } else if (Util.SDK_INT >= 24) { - int supportedLevel = - EncoderUtil.findHighestSupportedEncodingLevel( - encoderInfo, mimeType, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); - checkState(supportedLevel != EncoderUtil.LEVEL_UNSET); - // Use the baseline profile for safest results, as encoding in baseline is required per - // https://source.android.com/compatibility/5.0/android-5.0-cdd#5_2_video_encoding - mediaFormat.setInteger( - MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); - mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedLevel); - } - // For API levels below 24, setting profile and level can lead to failures in MediaCodec - // configuration. The encoder selects the profile/level when we don't set them. + adjustMediaFormatForH264EncoderSettings(mediaFormat, encoderInfo); } - mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, DEFAULT_COLOR_FORMAT); - mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL_SECS); + mediaFormat.setInteger( + MediaFormat.KEY_COLOR_FORMAT, supportedVideoEncoderSettings.colorProfile); + mediaFormat.setFloat( + MediaFormat.KEY_I_FRAME_INTERVAL, supportedVideoEncoderSettings.iFrameIntervalSeconds); return new DefaultCodec( format, @@ -199,8 +192,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { */ @RequiresNonNull("#1.sampleMimeType") @Nullable - private static Pair findEncoderWithClosestFormatSupport( + private static VideoEncoderQueryResult findEncoderWithClosestFormatSupport( Format requestedFormat, + VideoEncoderSettings videoEncoderSettings, EncoderSelector encoderSelector, List allowedMimeTypes, boolean enableFallback) { @@ -216,7 +210,8 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { return null; } if (!enableFallback) { - return Pair.create(encodersForMimeType.get(0), requestedFormat); + return new VideoEncoderQueryResult( + encodersForMimeType.get(0), requestedFormat, videoEncoderSettings); } ImmutableList filteredEncoders = filterEncoders( @@ -238,6 +233,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { if (filteredEncoders.isEmpty()) { return null; } + // The supported resolution is the same for all remaining encoders. Size finalResolution = checkNotNull( @@ -245,14 +241,10 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { filteredEncoders.get(0), mimeType, requestedFormat.width, requestedFormat.height)); int requestedBitrate = - requestedFormat.averageBitrate == Format.NO_VALUE - ? getSuggestedBitrate( - finalResolution.getWidth(), - finalResolution.getHeight(), - requestedFormat.frameRate == Format.NO_VALUE - ? DEFAULT_FRAME_RATE - : requestedFormat.frameRate) - : requestedFormat.averageBitrate; + videoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE + ? videoEncoderSettings.bitrate + : getSuggestedBitrate( + finalResolution.getWidth(), finalResolution.getHeight(), requestedFormat.frameRate); filteredEncoders = filterEncoders( filteredEncoders, @@ -266,32 +258,99 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { } MediaCodecInfo pickedEncoder = filteredEncoders.get(0); - @Nullable - Pair profileLevel = MediaCodecUtil.getCodecProfileAndLevel(requestedFormat); - @Nullable String codecs = null; - if (profileLevel != null - && requestedFormat.sampleMimeType.equals(mimeType) - && profileLevel.second - <= EncoderUtil.findHighestSupportedEncodingLevel( - pickedEncoder, mimeType, /* profile= */ profileLevel.first)) { - codecs = requestedFormat.codecs; + int closestSupportedBitrate = + EncoderUtil.getClosestSupportedBitrate(pickedEncoder, mimeType, requestedBitrate); + VideoEncoderSettings.Builder supportedEncodingSettingBuilder = + videoEncoderSettings.buildUpon().setBitrate(closestSupportedBitrate); + if (videoEncoderSettings.profile == VideoEncoderSettings.NO_VALUE + || videoEncoderSettings.level == VideoEncoderSettings.NO_VALUE + || videoEncoderSettings.level + > EncoderUtil.findHighestSupportedEncodingLevel( + pickedEncoder, mimeType, videoEncoderSettings.profile)) { + supportedEncodingSettingBuilder.setEncodingProfileLevel( + VideoEncoderSettings.NO_VALUE, VideoEncoderSettings.NO_VALUE); } - Format encoderSupportedFormat = + Format supportedEncoderFormat = requestedFormat .buildUpon() .setSampleMimeType(mimeType) - .setCodecs(codecs) .setWidth(finalResolution.getWidth()) .setHeight(finalResolution.getHeight()) - .setFrameRate( - requestedFormat.frameRate != Format.NO_VALUE - ? requestedFormat.frameRate - : DEFAULT_FRAME_RATE) - .setAverageBitrate( - EncoderUtil.getClosestSupportedBitrate(pickedEncoder, mimeType, requestedBitrate)) + .setAverageBitrate(closestSupportedBitrate) .build(); - return Pair.create(pickedEncoder, encoderSupportedFormat); + return new VideoEncoderQueryResult( + pickedEncoder, supportedEncoderFormat, supportedEncodingSettingBuilder.build()); + } + + private static final class VideoEncoderQueryResult { + public final MediaCodecInfo encoder; + public final Format supportedFormat; + public final VideoEncoderSettings supportedEncoderSettings; + + public VideoEncoderQueryResult( + MediaCodecInfo encoder, + Format supportedFormat, + VideoEncoderSettings supportedEncoderSettings) { + this.encoder = encoder; + this.supportedFormat = supportedFormat; + this.supportedEncoderSettings = supportedEncoderSettings; + } + } + + /** + * Applying suggested profile/level settings from + * https://developer.android.com/guide/topics/media/sharing-video#b-frames_and_encoding_profiles + * + *

The adjustment is applied in-place to {@code mediaFormat}. + */ + private static void adjustMediaFormatForH264EncoderSettings( + MediaFormat mediaFormat, MediaCodecInfo encoderInfo) { + // TODO(b/210593256): Remove overriding profile/level (before API 29) after switching to in-app + // muxing. + String mimeType = MimeTypes.VIDEO_H264; + if (Util.SDK_INT >= 29) { + int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh; + int supportedEncodingLevel = + EncoderUtil.findHighestSupportedEncodingLevel( + encoderInfo, mimeType, expectedEncodingProfile); + if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) { + // Use the highest supported profile and use B-frames. + mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel); + mediaFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 1); + } + } else if (Util.SDK_INT >= 26) { + int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh; + int supportedEncodingLevel = + EncoderUtil.findHighestSupportedEncodingLevel( + encoderInfo, mimeType, expectedEncodingProfile); + if (supportedEncodingLevel != EncoderUtil.LEVEL_UNSET) { + // Use the highest-supported profile, but disable the generation of B-frames using + // MediaFormat.KEY_LATENCY. This accommodates some limitations in the MediaMuxer in these + // system versions. + mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedEncodingLevel); + // TODO(b/210593256): Set KEY_LATENCY to 2 to enable B-frame production after switching to + // in-app muxing. + mediaFormat.setInteger(MediaFormat.KEY_LATENCY, 1); + } + } else if (Util.SDK_INT >= 24) { + int expectedEncodingProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline; + int supportedLevel = + EncoderUtil.findHighestSupportedEncodingLevel( + encoderInfo, mimeType, expectedEncodingProfile); + checkState(supportedLevel != EncoderUtil.LEVEL_UNSET); + // Use the baseline profile for safest results, as encoding in baseline is required per + // https://source.android.com/compatibility/5.0/android-5.0-cdd#5_2_video_encoding + mediaFormat.setInteger(MediaFormat.KEY_PROFILE, expectedEncodingProfile); + mediaFormat.setInteger(MediaFormat.KEY_LEVEL, supportedLevel); + } else { + // For API levels below 24, setting profile and level can lead to failures in MediaCodec + // configuration. The encoder selects the profile/level when we don't set them. + mediaFormat.setString(MediaFormat.KEY_PROFILE, null); + mediaFormat.setString(MediaFormat.KEY_LEVEL, null); + } } private interface EncoderFallbackCost { @@ -305,6 +364,15 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { int getParameterSupportGap(MediaCodecInfo encoderInfo); } + /** + * Filters a list of {@link MediaCodecInfo encoders} by a {@link EncoderFallbackCost cost + * function}. + * + * @param encoders A list of {@link MediaCodecInfo encoders}. + * @param cost A {@link EncoderFallbackCost cost function}. + * @return A list of {@link MediaCodecInfo encoders} with the lowest costs, empty if the costs of + * all encoders are {@link Integer#MAX_VALUE}. + */ private static ImmutableList filterEncoders( List encoders, EncoderFallbackCost cost) { List filteredEncoders = new ArrayList<>(encoders.size()); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java new file mode 100644 index 0000000000..c2f740c979 --- /dev/null +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoEncoderSettings.java @@ -0,0 +1,208 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * 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.google.android.exoplayer2.transformer; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static java.lang.annotation.ElementType.TYPE_USE; + +import android.media.MediaCodecInfo; +import androidx.annotation.IntDef; +import com.google.android.exoplayer2.Format; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Represents the video encoder settings. */ +public final class VideoEncoderSettings { + + /** A value for various fields to indicate that the field's value is unknown or not applicable. */ + public static final int NO_VALUE = Format.NO_VALUE; + /** The default encoding color profile. */ + public static final int DEFAULT_COLOR_PROFILE = + MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; + /** The default I-frame interval in seconds. */ + public static final float DEFAULT_I_FRAME_INTERVAL_SECONDS = 1.0f; + + /** + * The allowed values for {@code bitrateMode}, one of + * + *

+ */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({ + MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ, + MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR, + MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR, + MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR_FD + }) + public @interface BitrateMode {} + + /** Builds {@link VideoEncoderSettings} instances. */ + public static final class Builder { + private int bitrate; + private @BitrateMode int bitrateMode; + private int profile; + private int level; + private int colorProfile; + private float iFrameIntervalSeconds; + + /** Creates a new instance. */ + public Builder() { + this.bitrate = NO_VALUE; + this.bitrateMode = MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR; + this.profile = NO_VALUE; + this.level = NO_VALUE; + this.colorProfile = DEFAULT_COLOR_PROFILE; + this.iFrameIntervalSeconds = DEFAULT_I_FRAME_INTERVAL_SECONDS; + } + + private Builder(VideoEncoderSettings videoEncoderSettings) { + this.bitrate = videoEncoderSettings.bitrate; + this.bitrateMode = videoEncoderSettings.bitrateMode; + this.profile = videoEncoderSettings.profile; + this.level = videoEncoderSettings.level; + this.colorProfile = videoEncoderSettings.colorProfile; + this.iFrameIntervalSeconds = videoEncoderSettings.iFrameIntervalSeconds; + } + + /** + * Sets {@link VideoEncoderSettings#bitrate}. The default value is {@link #NO_VALUE}. + * + * @param bitrate The {@link VideoEncoderSettings#bitrate}. + * @return This builder. + */ + public Builder setBitrate(int bitrate) { + this.bitrate = bitrate; + return this; + } + + /** + * Sets {@link VideoEncoderSettings#bitrateMode}. The default value is {@code + * MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR}. + * + *

Only {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR} and {@link + * MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR} are allowed. + * + * @param bitrateMode The {@link VideoEncoderSettings#bitrateMode}. + * @return This builder. + */ + public Builder setBitrateMode(@BitrateMode int bitrateMode) { + checkArgument( + bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR + || bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); + this.bitrateMode = bitrateMode; + return this; + } + + /** + * Sets {@link VideoEncoderSettings#profile} and {@link VideoEncoderSettings#level}. The default + * values are both {@link #NO_VALUE}. + * + *

The value must be one of the values defined in {@link MediaCodecInfo.CodecProfileLevel}, + * or {@link #NO_VALUE}. + * + *

Profile and level settings will be ignored when using {@link DefaultEncoderFactory} and + * encoding to H264. + * + * @param encodingProfile The {@link VideoEncoderSettings#profile}. + * @param encodingLevel The {@link VideoEncoderSettings#level}. + * @return This builder. + */ + public Builder setEncodingProfileLevel(int encodingProfile, int encodingLevel) { + this.profile = encodingProfile; + this.level = encodingLevel; + return this; + } + + /** + * Sets {@link VideoEncoderSettings#colorProfile}. The default value is {@link + * #DEFAULT_COLOR_PROFILE}. + * + *

The value must be one of the {@code COLOR_*} constants defined in {@link + * MediaCodecInfo.CodecCapabilities}. + * + * @param colorProfile The {@link VideoEncoderSettings#colorProfile}. + * @return This builder. + */ + public Builder setColorProfile(int colorProfile) { + this.colorProfile = colorProfile; + return this; + } + + /** + * Sets {@link VideoEncoderSettings#iFrameIntervalSeconds}. The default value is {@link + * #DEFAULT_I_FRAME_INTERVAL_SECONDS}. + * + * @param iFrameIntervalSeconds The {@link VideoEncoderSettings#iFrameIntervalSeconds}. + * @return This builder. + */ + public Builder setiFrameIntervalSeconds(float iFrameIntervalSeconds) { + this.iFrameIntervalSeconds = iFrameIntervalSeconds; + return this; + } + + /** Builds the instance. */ + public VideoEncoderSettings build() { + return new VideoEncoderSettings( + bitrate, bitrateMode, profile, level, colorProfile, iFrameIntervalSeconds); + } + } + + /** The encoding bitrate. */ + public final int bitrate; + /** One of {@link BitrateMode the allowed modes}. */ + public final @BitrateMode int bitrateMode; + /** The encoding profile. */ + public final int profile; + /** The encoding level. */ + public final int level; + /** The encoding color profile. */ + public final int colorProfile; + /** The encoding I-Frame interval in seconds. */ + public final float iFrameIntervalSeconds; + + private VideoEncoderSettings( + int bitrate, + int bitrateMode, + int profile, + int level, + int colorProfile, + float iFrameIntervalSeconds) { + this.bitrate = bitrate; + this.bitrateMode = bitrateMode; + this.profile = profile; + this.level = level; + this.colorProfile = colorProfile; + this.iFrameIntervalSeconds = iFrameIntervalSeconds; + } + + /** + * Returns a {@link VideoEncoderSettings.Builder} initialized with the values of this instance. + */ + public Builder buildUpon() { + return new Builder(this); + } +} diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java index b899b5e6f8..1e060325e1 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java @@ -82,11 +82,13 @@ import org.checkerframework.dataflow.qual.Pure; .setWidth(requestedEncoderDimensions.getWidth()) .setHeight(requestedEncoderDimensions.getHeight()) .setRotationDegrees(0) + .setFrameRate(inputFormat.frameRate) .setSampleMimeType( transformationRequest.videoMimeType != null ? transformationRequest.videoMimeType : inputFormat.sampleMimeType) .build(); + encoder = encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes); Format encoderSupportedFormat = encoder.getConfigurationFormat(); fallbackListener.onTransformationRequestFinalized(