app/vid: take in account the stride when decoding.

This commit is contained in:
jkds
2026-01-08 11:27:35 +01:00
parent 6b0ddc2e24
commit 54974eeada
3 changed files with 66 additions and 15 deletions

View File

@@ -57,6 +57,8 @@ public class VideoDecoder {
private int decoderId;
private int outputColorFormat = -1;
private Context context;
private int stride = -1;
private int sliceHeight = -1;
/** Conditional logging based on DEBUG flag */
private void log(String fstr, Object... args) {
@@ -234,6 +236,14 @@ public class VideoDecoder {
outputColorFormat = newFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
log("Format changed: colorFormat=%d", outputColorFormat);
}
// Extract stride and slice-height for handling padded buffers
if (newFormat.containsKey(MediaFormat.KEY_STRIDE)) {
stride = newFormat.getInteger(MediaFormat.KEY_STRIDE);
}
if (newFormat.containsKey(MediaFormat.KEY_SLICE_HEIGHT)) {
sliceHeight = newFormat.getInteger(MediaFormat.KEY_SLICE_HEIGHT);
}
log("Format changed: stride=%d, slice-height=%d", stride, sliceHeight);
}
return 0;
}
@@ -245,6 +255,8 @@ public class VideoDecoder {
* - Semi-planar (NV21): De-interleaves UV data
* - Planar (YV12/I420): Reads U and V planes directly
*
* Handles stride padding when hardware decoder uses row alignment.
*
* @param outputBuffer Raw decoded frame data from MediaCodec
* @param offset Offset to valid data in buffer
* @param size Size of valid data in bytes
@@ -260,7 +272,24 @@ public class VideoDecoder {
byte[] uData = new byte[uvSize];
byte[] vData = new byte[uvSize];
outputBuffer.get(yData, 0, ySize);
// Read Y plane, accounting for stride if present
int yStride = (stride > 0) ? stride : width;
int yRows = (sliceHeight > 0) ? sliceHeight : height;
// Read Y plane row by row, skipping padding
for (int row = 0; row < height && row < yRows; row++) {
int destOffset = row * width;
outputBuffer.get(yData, destOffset, width);
if (row < yRows - 1) {
// Skip padding bytes to next row
int skip = yStride - width;
outputBuffer.position(outputBuffer.position() + skip);
}
}
// Seek to UV plane start: Y plane ends at yStride * yRows
int uvPlaneStart = yStride * yRows;
outputBuffer.position(offset + uvPlaneStart);
if (outputColorFormat == COLOR_FORMATYUV420_PLANAR) {
outputBuffer.get(uData, 0, uvSize);
@@ -269,7 +298,8 @@ public class VideoDecoder {
if (outputColorFormat != COLOR_FORMATYUV420_SEMIPLANAR) {
Log.w("darkfi", String.format("Unknown color format %d, assuming semi-planar", outputColorFormat));
}
deinterleaveUV(outputBuffer, uData, vData, uvSize);
// For semi-planar, UV plane may also have stride
deinterleaveUV(outputBuffer, uData, vData, uvSize, yStride);
}
onFrameDecoded(decoderId, yData, uData, vData, width, height);
@@ -279,17 +309,35 @@ public class VideoDecoder {
/**
* De-interleaves UV data from semi-planar YUV format (NV21).
*
* Handles stride padding in the UV plane.
*
* @param outputBuffer Buffer containing interleaved UVUV... data
* @param uData Output array for U component
* @param vData Output array for V component
* @param uvSize Number of UV pairs to de-interleave
* @param yStride Luma stride (used to calculate chroma stride)
*/
private void deinterleaveUV(ByteBuffer outputBuffer, byte[] uData, byte[] vData, int uvSize) {
byte[] uvInterleaved = new byte[uvSize * 2];
outputBuffer.get(uvInterleaved);
for (int i = 0; i < uvSize; i++) {
uData[i] = uvInterleaved[i * 2];
vData[i] = uvInterleaved[i * 2 + 1];
private void deinterleaveUV(ByteBuffer outputBuffer, byte[] uData, byte[] vData, int uvSize, int yStride) {
int uvWidth = width / 2;
int uvHeight = height / 2;
int uvStride = (yStride > 0) ? (yStride + 1) / 2 : uvWidth;
// Read UV plane row by row, deinterleaving and skipping padding
int uvIdx = 0;
for (int row = 0; row < uvHeight; row++) {
// Read one row of UV pairs (interleaved)
for (int col = 0; col < uvWidth; col++) {
int uByte = outputBuffer.get() & 0xFF;
int vByte = outputBuffer.get() & 0xFF;
uData[uvIdx] = (byte) uByte;
vData[uvIdx] = (byte) vByte;
uvIdx++;
}
// Skip padding to next row
if (row < uvHeight - 1) {
int skip = (uvStride - uvWidth) * 2;
outputBuffer.position(outputBuffer.position() + skip);
}
}
}
}

View File

@@ -21,14 +21,14 @@ use sled_overlay::sled;
use super::chat::populate_tree;
use crate::{
app::{
node::{create_chatview, create_layer, create_text, create_vector_art},
node::{create_chatview, create_layer, create_text, create_vector_art, create_video},
App,
},
expr::{self, Compiler},
mesh::COLOR_PURPLE,
prop::{PropertyAtomicGuard, PropertyFloat32, Role},
scene::SceneNodePtr,
ui::{ChatView, Layer, Text, VectorArt, VectorShape},
ui::{ChatView, Layer, Text, VectorArt, VectorShape, Video},
util::i18n::I18nBabelFish,
};
@@ -43,7 +43,7 @@ mod ui_consts {
get_appdata_path().join("chatdb")
}
//pub const KING_PATH: &str = "king.png";
//pub const VID_PATH: &str = "forest_720x1280.mp4";
pub const VID_PATH: &str = "forest_720x1280.mp4";
}
#[cfg(not(target_os = "android"))]
@@ -54,7 +54,7 @@ mod ui_consts {
"chatdb".into()
}
//pub const KING_PATH: &str = "assets/king.png";
//pub const VID_PATH: &str = "assets/forest_1920x1080.ivf";
pub const VID_PATH: &str = "assets/forest_1920x1080.ivf";
}
use ui_consts::*;
@@ -238,7 +238,6 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
*/
// Create KING GNU!
/*
let node = create_video("king");
let prop = node.get_property("rect").unwrap();
prop.set_f32(atom, Role::App, 0, 80.).unwrap();
@@ -249,8 +248,8 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
let node = node.setup(|me| Video::new(me, app.render_api.clone(), app.ex.clone())).await;
layer_node.link(node);
*/
/*
// Create some text
let node = create_text("label");
let prop = node.get_property("rect").unwrap();
@@ -367,7 +366,6 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
.await;
layer_node.link(node);
/*
// Text edit
let node = create_singleline_edit("editz");
//let node = create_multiline_edit("editz");

View File

@@ -72,6 +72,11 @@ pub fn spawn_decoder_thread(
let pct_loaded = 100. * frame_idx as f32 / 150.0;
d!("Decoded video {pct_loaded:.2}%%");
}
assert!(frame_idx <= 150);
if frame_idx == 150 {
break
}
}
d!("Finished decoding video: {path} in {:?}", now.elapsed());