fix(android): use MediaCodec input layout for video encode
Some checks failed
Mirror to GitHub / mirror (push) Failing after 29s
Build Release Binaries / build-amd64 (push) Failing after 4m1s

This commit is contained in:
Siavash Sameni
2026-05-26 11:35:24 +04:00
parent 112472609e
commit 3ea25a0656
2 changed files with 77 additions and 27 deletions

View File

@@ -19,7 +19,7 @@ shiguredo_svt_av1 = "2026.1.0"
shiguredo_video_toolbox = "2026.1" shiguredo_video_toolbox = "2026.1"
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
ndk = { version = "0.9", features = ["api-level-26", "media"] } ndk = { version = "0.9", features = ["api-level-28", "media"] }
[dev-dependencies] [dev-dependencies]
rand = "0.8" rand = "0.8"

View File

@@ -132,12 +132,12 @@ impl VideoEncoder for MediaCodecEncoder {
log_media_codec_input_format("h264_encoder_input", &self.codec, &layout); log_media_codec_input_format("h264_encoder_input", &self.codec, &layout);
} }
let input_capacity = { buffer.buffer_mut().len() }; let input_capacity = { buffer.buffer_mut().len() };
let mut input = i420_to_padded_nv12( let mut input = i420_to_encoder_input(
&frame.data, &frame.data,
self.width as usize, self.width as usize,
self.height as usize, self.height as usize,
layout.stride, &layout,
layout.slice_height, COLOR_FORMAT_YUV420_SEMIPLANAR,
)?; )?;
if input.len() > input_capacity { if input.len() > input_capacity {
tracing::warn!( tracing::warn!(
@@ -146,12 +146,17 @@ impl VideoEncoder for MediaCodecEncoder {
input_capacity, input_capacity,
"MediaCodec H.264 input buffer smaller than padded layout; falling back to tight NV12" "MediaCodec H.264 input buffer smaller than padded layout; falling back to tight NV12"
); );
input = i420_to_padded_nv12( let tight_layout = EncoderInputLayout {
color_format: layout.color_format,
stride: self.width as usize,
slice_height: self.height as usize,
};
input = i420_to_encoder_input(
&frame.data, &frame.data,
self.width as usize, self.width as usize,
self.height as usize, self.height as usize,
self.width as usize, &tight_layout,
self.height as usize, COLOR_FORMAT_YUV420_SEMIPLANAR,
)?; )?;
} }
let to_copy = { let to_copy = {
@@ -514,12 +519,12 @@ impl VideoEncoder for MediaCodecHevcEncoder {
log_media_codec_input_format("hevc_encoder_input", &self.codec, &layout); log_media_codec_input_format("hevc_encoder_input", &self.codec, &layout);
} }
let input_capacity = { buffer.buffer_mut().len() }; let input_capacity = { buffer.buffer_mut().len() };
let mut input = i420_to_padded_planar( let mut input = i420_to_encoder_input(
&frame.data, &frame.data,
self.width as usize, self.width as usize,
self.height as usize, self.height as usize,
layout.stride, &layout,
layout.slice_height, COLOR_FORMAT_YUV420_PLANAR,
)?; )?;
if input.len() > input_capacity { if input.len() > input_capacity {
tracing::warn!( tracing::warn!(
@@ -528,12 +533,17 @@ impl VideoEncoder for MediaCodecHevcEncoder {
input_capacity, input_capacity,
"MediaCodec HEVC input buffer smaller than padded layout; falling back to tight I420" "MediaCodec HEVC input buffer smaller than padded layout; falling back to tight I420"
); );
input = i420_to_padded_planar( let tight_layout = EncoderInputLayout {
color_format: layout.color_format,
stride: self.width as usize,
slice_height: self.height as usize,
};
input = i420_to_encoder_input(
&frame.data, &frame.data,
self.width as usize, self.width as usize,
self.height as usize, self.height as usize,
self.width as usize, &tight_layout,
self.height as usize, COLOR_FORMAT_YUV420_PLANAR,
)?; )?;
} }
let to_copy = { let to_copy = {
@@ -1216,43 +1226,83 @@ fn positive_format_usize(format: &MediaFormat, key: &str) -> Option<usize> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct EncoderInputLayout { struct EncoderInputLayout {
color_format: Option<i32>,
stride: usize, stride: usize,
slice_height: usize, slice_height: usize,
} }
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
fn encoder_input_layout(codec: &MediaCodec, width: u32, height: u32) -> EncoderInputLayout { fn encoder_input_layout(codec: &MediaCodec, width: u32, height: u32) -> EncoderInputLayout {
// ndk 0.9 exposes AMediaCodec_getInputFormat only behind API 28, while let format = codec.input_format();
// this app still targets API 26. Keep encoder input tight until we can
// query the actual input format. Guessing padded rows here is dangerous:
// when the encoder actually reads tight input, padding bytes become pixels
// from the next row and show up as diagonal green bands.
let _ = codec;
let width = width as usize; let width = width as usize;
let height = height as usize; let height = height as usize;
EncoderInputLayout { EncoderInputLayout {
stride: width, color_format: format.i32("color-format"),
slice_height: height, stride: positive_format_usize(&format, "stride")
.unwrap_or(width)
.max(width),
slice_height: positive_format_usize(&format, "slice-height")
.unwrap_or(height)
.max(height),
} }
} }
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
fn log_media_codec_input_format(label: &str, codec: &MediaCodec, layout: &EncoderInputLayout) { fn log_media_codec_input_format(label: &str, codec: &MediaCodec, layout: &EncoderInputLayout) {
let format = codec.output_format(); let input_format = codec.input_format();
let output_format = codec.output_format();
tracing::info!( tracing::info!(
target: "wzp_video::mediacodec", target: "wzp_video::mediacodec",
label, label,
color_format = format.i32("color-format"), input_color_format = input_format.i32("color-format"),
width = format.i32("width"), input_width = input_format.i32("width"),
height = format.i32("height"), input_height = input_format.i32("height"),
stride = format.i32("stride"), input_stride = input_format.i32("stride"),
slice_height = format.i32("slice-height"), input_slice_height = input_format.i32("slice-height"),
output_color_format = output_format.i32("color-format"),
output_width = output_format.i32("width"),
output_height = output_format.i32("height"),
output_stride = output_format.i32("stride"),
output_slice_height = output_format.i32("slice-height"),
effective_color_format = layout.color_format,
effective_stride = layout.stride, effective_stride = layout.stride,
effective_slice_height = layout.slice_height, effective_slice_height = layout.slice_height,
"MediaCodec input format" "MediaCodec input format"
); );
} }
#[cfg(target_os = "android")]
fn i420_to_encoder_input(
src: &[u8],
width: usize,
height: usize,
layout: &EncoderInputLayout,
default_color_format: i32,
) -> Result<Vec<u8>, VideoError> {
let color_format = layout.color_format.unwrap_or(default_color_format);
match color_format {
COLOR_FORMAT_YUV420_PLANAR => {
i420_to_padded_planar(src, width, height, layout.stride, layout.slice_height)
}
COLOR_FORMAT_YUV420_SEMIPLANAR => {
i420_to_padded_nv12(src, width, height, layout.stride, layout.slice_height)
}
other => {
tracing::warn!(
target: "wzp_video::mediacodec",
color_format = other,
default_color_format,
"unsupported MediaCodec encoder input color format; using requested default"
);
if default_color_format == COLOR_FORMAT_YUV420_PLANAR {
i420_to_padded_planar(src, width, height, layout.stride, layout.slice_height)
} else {
i420_to_padded_nv12(src, width, height, layout.stride, layout.slice_height)
}
}
}
}
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
fn log_media_codec_format(label: &str, codec: &MediaCodec) { fn log_media_codec_format(label: &str, codec: &MediaCodec) {
let format = codec.output_format(); let format = codec.output_format();