From d2046060b5d74460b580480f7149c540703bcda1 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Mon, 25 May 2026 21:28:59 +0400 Subject: [PATCH] fix(video): request android sync frames via mediacodec --- crates/wzp-video/Cargo.toml | 2 +- crates/wzp-video/src/mediacodec.rs | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/wzp-video/Cargo.toml b/crates/wzp-video/Cargo.toml index fdde0d3..cbd7125 100644 --- a/crates/wzp-video/Cargo.toml +++ b/crates/wzp-video/Cargo.toml @@ -19,7 +19,7 @@ shiguredo_svt_av1 = "2026.1.0" shiguredo_video_toolbox = "2026.1" [target.'cfg(target_os = "android")'.dependencies] -ndk = { version = "0.9", features = ["media"] } +ndk = { version = "0.9", features = ["api-level-26", "media"] } [dev-dependencies] rand = "0.8" diff --git a/crates/wzp-video/src/mediacodec.rs b/crates/wzp-video/src/mediacodec.rs index df87326..9347d29 100644 --- a/crates/wzp-video/src/mediacodec.rs +++ b/crates/wzp-video/src/mediacodec.rs @@ -48,6 +48,9 @@ const BITRATE_MODE_CBR: i32 = 2; /// AMediaCodec keyframe buffer flag. #[cfg(target_os = "android")] const AMEDIACODEC_BUFFER_FLAG_KEY_FRAME: u32 = 1; +/// MediaCodec encoder parameter key for forcing the next output frame to be a sync frame. +#[cfg(target_os = "android")] +const MEDIA_CODEC_REQUEST_SYNC_FRAME: &str = "request-sync"; // AMediaCodec is thread-safe; the NonNull inside MediaCodec suppresses auto-Send. #[cfg(target_os = "android")] @@ -117,12 +120,11 @@ impl VideoEncoder for MediaCodecEncoder { .dequeue_input_buffer(std::time::Duration::from_millis(10)) { Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(mut buffer)) => { - let flags = if self.force_keyframe { - AMEDIACODEC_BUFFER_FLAG_KEY_FRAME - } else { - 0 - }; - let input = i420_to_nv12(&frame.data, self.width as usize, self.height as usize)?; + if self.force_keyframe { + self.request_sync_frame(); + } + let input = + i420_to_nv12(&frame.data, self.width as usize, self.height as usize)?; let to_copy = { let buf = buffer.buffer_mut(); let n = input.len().min(buf.len()); @@ -132,7 +134,7 @@ impl VideoEncoder for MediaCodecEncoder { n }; self.codec - .queue_input_buffer(buffer, 0, to_copy, frame.timestamp_ms as u64 * 1000, flags) + .queue_input_buffer(buffer, 0, to_copy, frame.timestamp_ms as u64 * 1000, 0) .map_err(|e| { VideoError::PlatformError(format!("queue_input_buffer failed: {e}")) })?; @@ -174,6 +176,14 @@ impl VideoEncoder for MediaCodecEncoder { #[cfg(target_os = "android")] impl MediaCodecEncoder { + fn request_sync_frame(&self) { + let mut params = MediaFormat::new(); + params.set_i32(MEDIA_CODEC_REQUEST_SYNC_FRAME, 0); + if let Err(e) = self.codec.set_parameters(params) { + tracing::warn!(error = %e, "AMediaCodec request sync frame failed"); + } + } + /// Drain all available output buffers and convert from AVCC to Annex-B. fn drain_output(&mut self) -> Result, VideoError> { let mut output = Vec::new();