Tune MediaCodec static refresh budgets (#16579)

* Tune MediaCodec static refresh budgets

Use H.264 budgets 50/20/20/10 and H.265 budgets 90/20/20/20 from MT6855 1080p measurements with early stopping at 80% of observed-best improvement, not full convergence.

Retain the existing H.265 low-quality budget because the line-pattern sample did not confirm convergence within 150 repeat calls. Keep the existing ten latency-draining repeats, other backends, and refresh timing unchanged.

Extend output-budget and backend-selection regression coverage. Static refresh tests pass with hwcodec (17 tests) and default features (16 tests).

* fix(video): retain the largest static refresh budget per frame
This commit is contained in:
21pages authored and GitHub committed 2026-10-21 16:02:32 +08:00
1 parent ce54e33c71
commit 4e93a41ecc
1 file changed
+77 -2
+77 -2
View File
@@ -24,6 +24,7 @@ enum HardwareBackend {
Amf,
Vaapi,
VideoToolbox,
MediaCodec,
Other,
}
@@ -44,6 +45,8 @@ fn max_repeat_frames(codec: CodecFormat, quality: f32, backend: HardwareBackend)
(CodecFormat::H265, Amf) => (200, 200, 200, 120), // Original: (580, 200, 200, 120)
// The capture loop already repeats ten frames to drain VideoToolbox latency.
(CodecFormat::H265, VideoToolbox) => (90, 90, 50, 90), // Original: (230, 90, 50, 90)
(CodecFormat::H264, MediaCodec) => (50, 20, 20, 10),
(CodecFormat::H265, MediaCodec) => (90, 20, 20, 20),
(CodecFormat::H264, _) => (90, 60, 60, 30),
(CodecFormat::H265, _) => (90, 60, 60, 30),
(CodecFormat::AV1 | CodecFormat::Unknown, _) => return 0,
@@ -78,6 +81,7 @@ pub(super) struct StaticRefresh<'a> {
#[cfg(test)]
elapsed_since_encode: Duration,
repeat_output_counter: usize,
repeat_output_limit: usize,
repeat_no_outputs: usize,
repeat_failures: usize,
source_ready: bool,
@@ -104,6 +108,7 @@ impl<'a> StaticRefresh<'a> {
"h264_amf" | "hevc_amf" => HardwareBackend::Amf,
"h264_vaapi" => HardwareBackend::Vaapi,
"hevc_videotoolbox" => HardwareBackend::VideoToolbox,
"h264_mediacodec" | "hevc_mediacodec" => HardwareBackend::MediaCodec,
_ => HardwareBackend::Other,
},
#[cfg(feature = "vram")]
@@ -129,6 +134,7 @@ impl<'a> StaticRefresh<'a> {
#[cfg(test)]
elapsed_since_encode: Duration::ZERO,
repeat_output_counter: 0,
repeat_output_limit: 0,
repeat_no_outputs: 0,
repeat_failures: 0,
source_ready: false,
@@ -139,6 +145,7 @@ impl<'a> StaticRefresh<'a> {
pub(super) fn on_frame(&mut self, _frame: &EncodeInput) {
self.repeat_output_counter = 0;
self.repeat_output_limit = 0;
self.repeat_no_outputs = 0;
self.source_ready = false;
#[cfg(all(windows, feature = "vram"))]
@@ -182,6 +189,11 @@ impl<'a> StaticRefresh<'a> {
frame_controller: &mut VideoFrameController,
) -> ResultType<()> {
let spf = spf.max(Duration::from_secs(1) / MAX_REPEAT_FPS);
self.repeat_output_limit = self.repeat_output_limit.max(max_repeat_frames(
self.codec_format,
quality,
self.hardware_backend,
));
if !self.source.is_monitor()
|| self.codec_format == CodecFormat::AV1
|| !self.source_ready
@@ -191,8 +203,7 @@ impl<'a> StaticRefresh<'a> {
CodecFormat::VP8 | CodecFormat::VP9 | CodecFormat::AV1
))
|| self.repeat_no_outputs >= MAX_REPEAT_NO_OUTPUTS
|| self.repeat_output_counter
>= max_repeat_frames(self.codec_format, quality, self.hardware_backend)
|| self.repeat_output_counter >= self.repeat_output_limit
|| {
#[cfg(not(test))]
let elapsed = self.last_encode.elapsed();
@@ -586,6 +597,56 @@ mod tests {
sp.on_unsubscribe(1);
}
#[test]
fn quality_changes_preserve_the_largest_budget_until_a_new_frame() {
let sp = GenericService::new("repeat-quality-change-test".to_owned(), false);
let recorder = Arc::new(Mutex::new(None));
let mut refresh = new_refresh(
VideoSource::Monitor,
CodecFormat::H264,
&sp,
&recorder,
0,
1,
1,
);
refresh.hardware_backend = HardwareBackend::MediaCodec;
let (tx, rx) = mpsc::unbounded_channel();
sp.on_subscribe(ConnInner::new(1, None, Some(tx)));
let mut encoder = Encoder {
codec: Box::new(CountingEncoder(Rc::new(Cell::new(0)))),
};
for (new_frame, quality, attempts, expected) in [
(true, BR_BALANCED, 11, 11),
(false, BR_BEST, 20, 20),
(false, 0.1, 50, 50),
(false, BR_BEST, 50, 50),
(false, 0.1, 50, 50),
(true, BR_BEST, 20, 60),
] {
if new_frame {
refresh.on_frame(&EncodeInput::YUV(&[1]));
refresh.on_encoded(true);
}
for _ in 0..attempts {
refresh.elapsed_since_encode = Duration::from_secs(1);
refresh
.try_encode(
&[1],
Duration::from_millis(33),
quality,
Instant::now(),
0,
&mut encoder,
&mut VideoFrameController::new(0),
)
.unwrap();
}
assert_eq!(rx.len(), expected);
}
sp.on_unsubscribe(1);
}
#[test]
fn quality_bands_stop_each_backend_at_its_output_budget() {
let sp = GenericService::new("repeat-quality-test".to_owned(), false);
@@ -595,6 +656,16 @@ mod tests {
.map(|q| { max_repeat_frames(CodecFormat::H264, q, HardwareBackend::Vaapi) }),
[150, 100, 90, 60]
);
assert_eq!(
[0.1, BR_SPEED, BR_BALANCED, BR_BEST]
.map(|q| { max_repeat_frames(CodecFormat::H264, q, HardwareBackend::MediaCodec) }),
[50, 20, 20, 10]
);
assert_eq!(
[0.1, BR_SPEED, BR_BALANCED, BR_BEST]
.map(|q| { max_repeat_frames(CodecFormat::H265, q, HardwareBackend::MediaCodec) }),
[90, 20, 20, 20]
);
for codec in [
CodecFormat::VP8,
CodecFormat::VP9,
@@ -607,6 +678,7 @@ mod tests {
HardwareBackend::Amf,
HardwareBackend::Vaapi,
HardwareBackend::VideoToolbox,
HardwareBackend::MediaCodec,
HardwareBackend::Other,
] {
let mut refresh = new_refresh(VideoSource::Monitor, codec, &sp, &recorder, 0, 1, 1);
@@ -640,6 +712,7 @@ mod tests {
HardwareBackend::Amf,
HardwareBackend::Vaapi,
HardwareBackend::VideoToolbox,
HardwareBackend::MediaCodec,
HardwareBackend::Other,
] {
assert_eq!(
@@ -667,6 +740,8 @@ mod tests {
("hevc_vaapi", HardwareBackend::Other),
("hevc_videotoolbox", HardwareBackend::VideoToolbox),
("h264_videotoolbox", HardwareBackend::Other),
("h264_mediacodec", HardwareBackend::MediaCodec),
("hevc_mediacodec", HardwareBackend::MediaCodec),
] {
let config = EncoderCfg::HWRAM(scrap::hwcodec::HwRamEncoderConfig {
name: name.to_owned(),