Add encoder setting to set frame repeat interval

This can be used for screen recording to produce files without large gaps
between frames.

PiperOrigin-RevId: 726451010
This commit is contained in:
andrewlewis 2025-02-13 06:04:00 -08:00 committed by Copybara-Service
parent 2a91d47ea9
commit 04d9a751c6
3 changed files with 74 additions and 0 deletions

View File

@ -385,6 +385,13 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
}
}
long repeatPreviousFrameIntervalUs =
supportedVideoEncoderSettings.repeatPreviousFrameIntervalUs;
if (repeatPreviousFrameIntervalUs != VideoEncoderSettings.NO_VALUE) {
mediaFormat.setLong(
MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, repeatPreviousFrameIntervalUs);
}
if (Util.SDK_INT >= 35) {
mediaFormat.setInteger(MediaFormat.KEY_IMPORTANCE, max(0, -codecPriority));
}

View File

@ -82,6 +82,7 @@ public final class VideoEncoderSettings {
private float iFrameIntervalSeconds;
private int operatingRate;
private int priority;
private long repeatPreviousFrameIntervalUs;
private boolean enableHighQualityTargeting;
/** Creates a new instance. */
@ -93,6 +94,7 @@ public final class VideoEncoderSettings {
this.iFrameIntervalSeconds = DEFAULT_I_FRAME_INTERVAL_SECONDS;
this.operatingRate = NO_VALUE;
this.priority = NO_VALUE;
this.repeatPreviousFrameIntervalUs = NO_VALUE;
}
private Builder(VideoEncoderSettings videoEncoderSettings) {
@ -103,6 +105,7 @@ public final class VideoEncoderSettings {
this.iFrameIntervalSeconds = videoEncoderSettings.iFrameIntervalSeconds;
this.operatingRate = videoEncoderSettings.operatingRate;
this.priority = videoEncoderSettings.priority;
this.repeatPreviousFrameIntervalUs = videoEncoderSettings.repeatPreviousFrameIntervalUs;
this.enableHighQualityTargeting = videoEncoderSettings.enableHighQualityTargeting;
}
@ -190,6 +193,21 @@ public final class VideoEncoderSettings {
return this;
}
/**
* Sets the threshold duration between input frames beyond which to repeat the previous frame if
* no new frame has been received, in microseconds. The default value is {@link #NO_VALUE},
* which means that frames are not automatically repeated.
*
* @param repeatPreviousFrameIntervalUs The {@linkplain
* MediaFormat#KEY_REPEAT_PREVIOUS_FRAME_AFTER frame repeat interval}, in microseconds.
* @return This builder.
*/
@CanIgnoreReturnValue
public Builder setRepeatPreviousFrameIntervalUs(long repeatPreviousFrameIntervalUs) {
this.repeatPreviousFrameIntervalUs = repeatPreviousFrameIntervalUs;
return this;
}
/**
* Sets whether to enable automatic adjustment of the bitrate to target a high quality encoding.
*
@ -223,6 +241,7 @@ public final class VideoEncoderSettings {
iFrameIntervalSeconds,
operatingRate,
priority,
repeatPreviousFrameIntervalUs,
enableHighQualityTargeting);
}
}
@ -248,6 +267,12 @@ public final class VideoEncoderSettings {
/** The encoder {@link MediaFormat#KEY_PRIORITY priority}. */
public final int priority;
/**
* The {@linkplain MediaFormat#KEY_REPEAT_PREVIOUS_FRAME_AFTER frame repeat interval}, in
* microseconds.
*/
public final long repeatPreviousFrameIntervalUs;
/** Whether the encoder should automatically set the bitrate to target a high quality encoding. */
public final boolean enableHighQualityTargeting;
@ -259,6 +284,7 @@ public final class VideoEncoderSettings {
float iFrameIntervalSeconds,
int operatingRate,
int priority,
long repeatPreviousFrameIntervalUs,
boolean enableHighQualityTargeting) {
this.bitrate = bitrate;
this.bitrateMode = bitrateMode;
@ -267,6 +293,7 @@ public final class VideoEncoderSettings {
this.iFrameIntervalSeconds = iFrameIntervalSeconds;
this.operatingRate = operatingRate;
this.priority = priority;
this.repeatPreviousFrameIntervalUs = repeatPreviousFrameIntervalUs;
this.enableHighQualityTargeting = enableHighQualityTargeting;
}
@ -293,6 +320,7 @@ public final class VideoEncoderSettings {
&& iFrameIntervalSeconds == that.iFrameIntervalSeconds
&& operatingRate == that.operatingRate
&& priority == that.priority
&& repeatPreviousFrameIntervalUs == that.repeatPreviousFrameIntervalUs
&& enableHighQualityTargeting == that.enableHighQualityTargeting;
}
@ -306,6 +334,9 @@ public final class VideoEncoderSettings {
result = 31 * result + Float.floatToIntBits(iFrameIntervalSeconds);
result = 31 * result + operatingRate;
result = 31 * result + priority;
result =
31 * result
+ (int) (repeatPreviousFrameIntervalUs ^ (repeatPreviousFrameIntervalUs >>> 32));
result = 31 * result + (enableHighQualityTargeting ? 1 : 0);
return result;
}

View File

@ -323,6 +323,42 @@ public class DefaultEncoderFactoryTest {
assertThat(configurationMediaFormat.containsKey(MediaFormat.KEY_OPERATING_RATE)).isFalse();
}
@Test
public void
createForVideoEncoding_withRepeatPreviousFrameIntervalUs_configuresEncoderWithRepeatPreviousFrameIntervalUs()
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
DefaultCodec videoEncoder =
new DefaultEncoderFactory.Builder(context)
.setRequestedVideoEncoderSettings(
new VideoEncoderSettings.Builder().setRepeatPreviousFrameIntervalUs(33_333).build())
.build()
.createForVideoEncoding(requestedVideoFormat);
assertThat(
videoEncoder
.getConfigurationMediaFormat()
.getLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER))
.isEqualTo(33_333);
}
@Test
public void
createForVideoEncoding_withDefaultEncoderSettings_doesNotConfigureRepeatPreviousFrameIntervalUs()
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
DefaultCodec videoEncoder =
new DefaultEncoderFactory.Builder(context)
.build()
.createForVideoEncoding(requestedVideoFormat);
assertThat(
videoEncoder
.getConfigurationMediaFormat()
.containsKey(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER))
.isFalse();
}
@Test
public void createForVideoEncoding_withNoAvailableEncoderFromEncoderSelector_throws() {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);