Add encoding options.

PiperOrigin-RevId: 435398814
This commit is contained in:
claincly 2022-03-17 18:22:42 +00:00 committed by Ian Baker
parent d1b99f0103
commit 79a6c806da
4 changed files with 383 additions and 103 deletions

View File

@ -95,8 +95,10 @@ public interface Codec {
* *
* @param format The {@link Format} (of the output data) used to determine the underlying * @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} * 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 * and {@link Format#height} are set to those of the desired output video format. {@link
* Format#rotationDegrees} should be 0. The video should always be in landscape orientation. * 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 * @param allowedMimeTypes The non-empty list of allowed output sample {@link MimeTypes MIME
* types}. * types}.
* @return A {@link Codec} for video encoding. * @return A {@link Codec} for video encoding.

View File

@ -29,7 +29,6 @@ import android.util.Pair;
import android.util.Size; import android.util.Size;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format; 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.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -38,32 +37,50 @@ import java.util.List;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** A default implementation of {@link Codec.EncoderFactory}. */ /** A default implementation of {@link Codec.EncoderFactory}. */
// TODO(b/224949986) Split audio and video encoder factory.
public final class DefaultEncoderFactory implements Codec.EncoderFactory { public final class DefaultEncoderFactory implements Codec.EncoderFactory {
private static final int DEFAULT_COLOR_FORMAT = private static final int DEFAULT_FRAME_RATE = 30;
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
private static final int DEFAULT_FRAME_RATE = 60;
private static final int DEFAULT_I_FRAME_INTERVAL_SECS = 1;
@Nullable private final EncoderSelector videoEncoderSelector; private final EncoderSelector videoEncoderSelector;
private final VideoEncoderSettings requestedVideoEncoderSettings;
private final boolean enableFallback; private final boolean enableFallback;
/** /**
* Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, and * Creates a new instance using the {@link EncoderSelector#DEFAULT default encoder selector}, a
* format fallback enabled. * default {@link VideoEncoderSettings}, and with format fallback enabled.
*
* <p>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.
*/ */
public DefaultEncoderFactory() { public DefaultEncoderFactory() {
this(EncoderSelector.DEFAULT, /* enableFallback= */ true); 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.
*
* <p>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.
*
* <p>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( public DefaultEncoderFactory(
@Nullable EncoderSelector videoEncoderSelector, boolean enableFallback) { EncoderSelector videoEncoderSelector,
VideoEncoderSettings requestedVideoEncoderSettings,
boolean enableFallback) {
this.videoEncoderSelector = videoEncoderSelector; this.videoEncoderSelector = videoEncoderSelector;
this.requestedVideoEncoderSettings = requestedVideoEncoderSettings;
this.enableFallback = enableFallback; this.enableFallback = enableFallback;
} }
@ -99,6 +116,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
@Override @Override
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
throws TransformationException { throws TransformationException {
if (format.frameRate == Format.NO_VALUE) {
format = format.buildUpon().setFrameRate(DEFAULT_FRAME_RATE).build();
}
checkArgument(format.width != Format.NO_VALUE); checkArgument(format.width != Format.NO_VALUE);
checkArgument(format.height != Format.NO_VALUE); checkArgument(format.height != Format.NO_VALUE);
// According to interface Javadoc, format.rotationDegrees should be 0. The video should always // 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); checkStateNotNull(videoEncoderSelector);
@Nullable @Nullable
Pair<MediaCodecInfo, Format> encoderAndClosestFormatSupport = VideoEncoderQueryResult encoderAndClosestFormatSupport =
findEncoderWithClosestFormatSupport( findEncoderWithClosestFormatSupport(
format, videoEncoderSelector, allowedMimeTypes, enableFallback); format,
requestedVideoEncoderSettings,
videoEncoderSelector,
allowedMimeTypes,
enableFallback);
if (encoderAndClosestFormatSupport == null) { if (encoderAndClosestFormatSupport == null) {
throw createTransformationException(format); throw createTransformationException(format);
} }
MediaCodecInfo encoderInfo = encoderAndClosestFormatSupport.first; MediaCodecInfo encoderInfo = encoderAndClosestFormatSupport.encoder;
format = encoderAndClosestFormatSupport.second; format = encoderAndClosestFormatSupport.supportedFormat;
VideoEncoderSettings supportedVideoEncoderSettings =
encoderAndClosestFormatSupport.supportedEncoderSettings;
String mimeType = checkNotNull(format.sampleMimeType); String mimeType = checkNotNull(format.sampleMimeType);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, format.width, format.height); MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, format.width, format.height);
mediaFormat.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate); 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 mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, supportedVideoEncoderSettings.bitrateMode);
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
if (codecProfileAndLevel != null) { if (supportedVideoEncoderSettings.profile != VideoEncoderSettings.NO_VALUE
// The codecProfileAndLevel is supported by the encoder. && supportedVideoEncoderSettings.level != VideoEncoderSettings.NO_VALUE
mediaFormat.setInteger(MediaFormat.KEY_PROFILE, codecProfileAndLevel.first); && SDK_INT >= 23) {
if (SDK_INT >= 23) { // Set profile and level at the same time to maximize compatibility, or the encoder will pick
mediaFormat.setInteger(MediaFormat.KEY_LEVEL, codecProfileAndLevel.second); // 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)) { if (mimeType.equals(MimeTypes.VIDEO_H264)) {
// Applying suggested profile/level settings from adjustMediaFormatForH264EncoderSettings(mediaFormat, encoderInfo);
// 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.
} }
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, DEFAULT_COLOR_FORMAT); mediaFormat.setInteger(
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL_SECS); MediaFormat.KEY_COLOR_FORMAT, supportedVideoEncoderSettings.colorProfile);
mediaFormat.setFloat(
MediaFormat.KEY_I_FRAME_INTERVAL, supportedVideoEncoderSettings.iFrameIntervalSeconds);
return new DefaultCodec( return new DefaultCodec(
format, format,
@ -199,8 +192,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
*/ */
@RequiresNonNull("#1.sampleMimeType") @RequiresNonNull("#1.sampleMimeType")
@Nullable @Nullable
private static Pair<MediaCodecInfo, Format> findEncoderWithClosestFormatSupport( private static VideoEncoderQueryResult findEncoderWithClosestFormatSupport(
Format requestedFormat, Format requestedFormat,
VideoEncoderSettings videoEncoderSettings,
EncoderSelector encoderSelector, EncoderSelector encoderSelector,
List<String> allowedMimeTypes, List<String> allowedMimeTypes,
boolean enableFallback) { boolean enableFallback) {
@ -216,7 +210,8 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
return null; return null;
} }
if (!enableFallback) { if (!enableFallback) {
return Pair.create(encodersForMimeType.get(0), requestedFormat); return new VideoEncoderQueryResult(
encodersForMimeType.get(0), requestedFormat, videoEncoderSettings);
} }
ImmutableList<MediaCodecInfo> filteredEncoders = ImmutableList<MediaCodecInfo> filteredEncoders =
filterEncoders( filterEncoders(
@ -238,6 +233,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
if (filteredEncoders.isEmpty()) { if (filteredEncoders.isEmpty()) {
return null; return null;
} }
// The supported resolution is the same for all remaining encoders. // The supported resolution is the same for all remaining encoders.
Size finalResolution = Size finalResolution =
checkNotNull( checkNotNull(
@ -245,14 +241,10 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
filteredEncoders.get(0), mimeType, requestedFormat.width, requestedFormat.height)); filteredEncoders.get(0), mimeType, requestedFormat.width, requestedFormat.height));
int requestedBitrate = int requestedBitrate =
requestedFormat.averageBitrate == Format.NO_VALUE videoEncoderSettings.bitrate != VideoEncoderSettings.NO_VALUE
? getSuggestedBitrate( ? videoEncoderSettings.bitrate
finalResolution.getWidth(), : getSuggestedBitrate(
finalResolution.getHeight(), finalResolution.getWidth(), finalResolution.getHeight(), requestedFormat.frameRate);
requestedFormat.frameRate == Format.NO_VALUE
? DEFAULT_FRAME_RATE
: requestedFormat.frameRate)
: requestedFormat.averageBitrate;
filteredEncoders = filteredEncoders =
filterEncoders( filterEncoders(
filteredEncoders, filteredEncoders,
@ -266,32 +258,99 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
} }
MediaCodecInfo pickedEncoder = filteredEncoders.get(0); MediaCodecInfo pickedEncoder = filteredEncoders.get(0);
@Nullable int closestSupportedBitrate =
Pair<Integer, Integer> profileLevel = MediaCodecUtil.getCodecProfileAndLevel(requestedFormat); EncoderUtil.getClosestSupportedBitrate(pickedEncoder, mimeType, requestedBitrate);
@Nullable String codecs = null; VideoEncoderSettings.Builder supportedEncodingSettingBuilder =
if (profileLevel != null videoEncoderSettings.buildUpon().setBitrate(closestSupportedBitrate);
&& requestedFormat.sampleMimeType.equals(mimeType) if (videoEncoderSettings.profile == VideoEncoderSettings.NO_VALUE
&& profileLevel.second || videoEncoderSettings.level == VideoEncoderSettings.NO_VALUE
<= EncoderUtil.findHighestSupportedEncodingLevel( || videoEncoderSettings.level
pickedEncoder, mimeType, /* profile= */ profileLevel.first)) { > EncoderUtil.findHighestSupportedEncodingLevel(
codecs = requestedFormat.codecs; pickedEncoder, mimeType, videoEncoderSettings.profile)) {
supportedEncodingSettingBuilder.setEncodingProfileLevel(
VideoEncoderSettings.NO_VALUE, VideoEncoderSettings.NO_VALUE);
} }
Format encoderSupportedFormat = Format supportedEncoderFormat =
requestedFormat requestedFormat
.buildUpon() .buildUpon()
.setSampleMimeType(mimeType) .setSampleMimeType(mimeType)
.setCodecs(codecs)
.setWidth(finalResolution.getWidth()) .setWidth(finalResolution.getWidth())
.setHeight(finalResolution.getHeight()) .setHeight(finalResolution.getHeight())
.setFrameRate( .setAverageBitrate(closestSupportedBitrate)
requestedFormat.frameRate != Format.NO_VALUE
? requestedFormat.frameRate
: DEFAULT_FRAME_RATE)
.setAverageBitrate(
EncoderUtil.getClosestSupportedBitrate(pickedEncoder, mimeType, requestedBitrate))
.build(); .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
*
* <p>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 { private interface EncoderFallbackCost {
@ -305,6 +364,15 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
int getParameterSupportGap(MediaCodecInfo encoderInfo); 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<MediaCodecInfo> filterEncoders( private static ImmutableList<MediaCodecInfo> filterEncoders(
List<MediaCodecInfo> encoders, EncoderFallbackCost cost) { List<MediaCodecInfo> encoders, EncoderFallbackCost cost) {
List<MediaCodecInfo> filteredEncoders = new ArrayList<>(encoders.size()); List<MediaCodecInfo> filteredEncoders = new ArrayList<>(encoders.size());

View File

@ -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
*
* <ul>
* <li>Constant quality: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CQ}.
* <li>Variable bitrate: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_VBR}.
* <li>Constant bitrate: {@link MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR}.
* <li>Constant bitrate with frame drops: {@link
* MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CBR_FD}, available from API31.
* </ul>
*/
@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}.
*
* <p>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}.
*
* <p>The value must be one of the values defined in {@link MediaCodecInfo.CodecProfileLevel},
* or {@link #NO_VALUE}.
*
* <p>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}.
*
* <p>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);
}
}

View File

@ -82,11 +82,13 @@ import org.checkerframework.dataflow.qual.Pure;
.setWidth(requestedEncoderDimensions.getWidth()) .setWidth(requestedEncoderDimensions.getWidth())
.setHeight(requestedEncoderDimensions.getHeight()) .setHeight(requestedEncoderDimensions.getHeight())
.setRotationDegrees(0) .setRotationDegrees(0)
.setFrameRate(inputFormat.frameRate)
.setSampleMimeType( .setSampleMimeType(
transformationRequest.videoMimeType != null transformationRequest.videoMimeType != null
? transformationRequest.videoMimeType ? transformationRequest.videoMimeType
: inputFormat.sampleMimeType) : inputFormat.sampleMimeType)
.build(); .build();
encoder = encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes); encoder = encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes);
Format encoderSupportedFormat = encoder.getConfigurationFormat(); Format encoderSupportedFormat = encoder.getConfigurationFormat();
fallbackListener.onTransformationRequestFinalized( fallbackListener.onTransformationRequestFinalized(