From 928dcd984dd15e087ea3238938cc432d189abc52 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 19 Sep 2026 13:24:02 +0800 Subject: [PATCH 1/3] fix: reallocate RAM decode buffer on resolution changes Unreference the cached software frame when decoded dimensions change, so hardware frame transfers allocate a correctly sized output buffer. Signed-off-by: 21pages --- cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp b/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp index fdd8a27..4ea6948 100644 --- a/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp +++ b/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp @@ -197,6 +197,10 @@ class FFmpegRamDecoder { LOG_ERROR(std::string("hw_frames_ctx is NULL")); goto _exit; } + if (sw_frame_->width != frame_->width || + sw_frame_->height != frame_->height) { + av_frame_unref(sw_frame_); + } if ((ret = av_hwframe_transfer_data(sw_frame_, frame_, 0)) < 0) { LOG_ERROR(std::string("av_hwframe_transfer_data failed, ret = ") + av_err2str(ret)); From df3a224defa732c7889627e6fc3c8d9b1241f3ec Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 19 Sep 2026 14:45:56 +0800 Subject: [PATCH 2/3] decode resolution example Signed-off-by: 21pages --- examples/decode_resolution.rs | 183 ++++++++++++++++++++++ examples/res/decode_resolution/1080p.h264 | Bin 0 -> 1096 bytes examples/res/decode_resolution/1080p.h265 | Bin 0 -> 691 bytes examples/res/decode_resolution/360p.h264 | Bin 0 -> 361 bytes examples/res/decode_resolution/360p.h265 | Bin 0 -> 197 bytes 5 files changed, 183 insertions(+) create mode 100644 examples/decode_resolution.rs create mode 100644 examples/res/decode_resolution/1080p.h264 create mode 100644 examples/res/decode_resolution/1080p.h265 create mode 100644 examples/res/decode_resolution/360p.h264 create mode 100644 examples/res/decode_resolution/360p.h265 diff --git a/examples/decode_resolution.rs b/examples/decode_resolution.rs new file mode 100644 index 0000000..f34a92c --- /dev/null +++ b/examples/decode_resolution.rs @@ -0,0 +1,183 @@ +//! Reuse each available decoder across resolution decreases and increases. +//! Run with `cargo run --example decode_resolution --features vram -- 100`. +//! The optional argument is the number of rounds (default: 1). + +use hwcodec::{ + common::DataFormat, + ffmpeg_ram::decode::{DecodeContext, Decoder as RamDecoder}, +}; +use std::{env, process::ExitCode}; + +struct Sample { + width: i32, + height: i32, + h264: &'static [u8], + h265: &'static [u8], +} + +const SAMPLES: [Sample; 3] = [ + Sample { + width: 640, + height: 360, + h264: include_bytes!("res/decode_resolution/360p.h264"), + h265: include_bytes!("res/decode_resolution/360p.h265"), + }, + Sample { + width: 1280, + height: 720, + h264: include_bytes!("../src/res/720p.h264"), + h265: include_bytes!("../src/res/720p.h265"), + }, + Sample { + width: 1920, + height: 1080, + h264: include_bytes!("res/decode_resolution/1080p.h264"), + h265: include_bytes!("res/decode_resolution/1080p.h265"), + }, +]; + +const SEQUENCE: [usize; 4] = [1, 0, 2, 1]; + +enum Decoder { + Ram(RamDecoder), + #[cfg(all(windows, feature = "vram"))] + Vram(hwcodec::vram::decode::Decoder), +} + +impl Decoder { + fn decode(&mut self, sample: &Sample, format: DataFormat) -> Result<(), String> { + let packet = match format { + DataFormat::H264 => sample.h264, + DataFormat::H265 => sample.h265, + _ => return Err(format!("unsupported sample format: {format:?}")), + }; + match self { + Self::Ram(decoder) => { + let frames = decoder.decode(packet).map_err(|e| format!("decode: {e}"))?; + check_dimensions(sample, frames.iter().map(|f| (f.width, f.height))) + } + #[cfg(all(windows, feature = "vram"))] + Self::Vram(decoder) => { + let frames = decoder.decode(packet).map_err(|e| format!("decode: {e}"))?; + if frames.iter().any(|frame| frame.texture.is_null()) { + return Err("decoder returned a null texture".to_owned()); + } + check_dimensions(sample, frames.iter().map(|f| (f.width, f.height))) + } + } + } +} + +fn check_dimensions( + sample: &Sample, + frames: impl Iterator, +) -> Result<(), String> { + let mut count = 0; + for (width, height) in frames { + if (width, height) != (sample.width, sample.height) { + return Err(format!( + "expected {}x{}, got {width}x{height}", + sample.width, sample.height + )); + } + count += 1; + } + if count != 1 { + return Err(format!("expected one decoded frame, got {count}")); + } + Ok(()) +} + +fn run_case(label: &str, decoder: Result, format: DataFormat, rounds: usize) -> bool { + let result = decoder + .map_err(|_| "decoder creation failed".to_owned()) + .and_then(|mut decoder| { + for round in 1..=rounds { + for (step, &index) in SEQUENCE.iter().enumerate() { + let sample = &SAMPLES[index]; + decoder.decode(sample, format).map_err(|error| { + format!( + "round {round}, step {} ({}x{}): {error}", + step + 1, + sample.width, + sample.height + ) + })?; + } + } + Ok(()) + }); + match result { + Ok(()) => { + println!("PASS {label}: {rounds} rounds"); + true + } + Err(error) => { + eprintln!("FAIL {label}: {error}"); + false + } + } +} + +fn main() -> ExitCode { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); + let mut args = env::args().skip(1); + let rounds = match args.next() { + Some(value) => match value.parse::() { + Ok(rounds) if rounds > 0 => rounds, + _ => { + eprintln!("Usage: decode_resolution [positive number of rounds]"); + return ExitCode::FAILURE; + } + }, + None => 1, + }; + if args.next().is_some() { + eprintln!("Usage: decode_resolution [positive number of rounds]"); + return ExitCode::FAILURE; + } + + println!("One decoder per case, {rounds} rounds, no reset between frames."); + println!("Sequence: 1280x720 -> 640x360 -> 1920x1080 -> 1280x720"); + let mut total = 0; + let mut passed = 0; + for codec in RamDecoder::available_decoders() { + let label = format!("RAM {} {:?}", codec.name, codec.hwdevice); + let context = DecodeContext { + name: codec.name, + device_type: codec.hwdevice, + thread_count: 1, + }; + total += 1; + passed += usize::from(run_case( + &label, + RamDecoder::new(context).map(Decoder::Ram), + codec.format, + rounds, + )); + } + #[cfg(all(windows, feature = "vram"))] + for context in hwcodec::vram::decode::available() { + let label = format!( + "VRAM {:?} {:?} LUID={} {:?}", + context.driver, context.vendor, context.luid, context.data_format + ); + let format = context.data_format; + total += 1; + passed += usize::from(run_case( + &label, + hwcodec::vram::decode::Decoder::new(context).map(Decoder::Vram), + format, + rounds, + )); + } + #[cfg(all(windows, not(feature = "vram")))] + println!("VRAM enumeration requires --features vram."); + + println!("Result: {passed}/{total} decoder configurations passed."); + if total > 0 && passed == total { + ExitCode::SUCCESS + } else { + ExitCode::FAILURE + } +} diff --git a/examples/res/decode_resolution/1080p.h264 b/examples/res/decode_resolution/1080p.h264 new file mode 100644 index 0000000000000000000000000000000000000000..1164220e45e7820c3d5b565f6139bb698e8c5f0e GIT binary patch literal 1096 zcmZQzU|>x5bkx~0U)l6;#8>;Ei;owC*Ly??oe}Yyfi%wD!5ZX`WfRJ*cs-Z{$A^(V0 nCUYiGHLXEX1XmAL4MhqF?k}`5nKy>2CPM;(uY;$QA!2wbW zCO$ZTl)Eq-ZZa4hYeEk2-)~WRuu3QtGpxb(L z2GlNyjR?|g!Ajl}Y%iXB;E-o#V2hc|Z}I@AG=ro;J8ns!|8jsX1bY)ZX`O&inpFp% ztP&n)F*7V`z@wLOA|6SmTeu}zeQ?WhMBtX=`+-|dYy)mNc|3m9#v69n!y8XjU`Q!H F004xwI8guq literal 0 HcmV?d00001 diff --git a/examples/res/decode_resolution/360p.h264 b/examples/res/decode_resolution/360p.h264 new file mode 100644 index 0000000000000000000000000000000000000000..3c616d96efcf11a1c639ef84152f72998235b7f1 GIT binary patch literal 361 zcmZQzU|>x5b&$Ksut5K3fgl3|GXo=tu;~}m^I!lcC@gp|Mz|E{YKxz z+NRmN!Y#I(UOmOcHt^e&Wfj|iN}0ik|HIjR^8-b%PhSS&)-ebkh-|S1X=j|IBp|e( f90M8}LHZfCQ0M>#Hzh?z?f(?GAc|}QfMEatmy3go literal 0 HcmV?d00001 diff --git a/examples/res/decode_resolution/360p.h265 b/examples/res/decode_resolution/360p.h265 new file mode 100644 index 0000000000000000000000000000000000000000..486058a8ec604830f71ef4a92fa9d38f529480a3 GIT binary patch literal 197 zcmZQzU|@7$(u%SSFQ<8&~3drgMopqW+m?lwinMm kz;^L^Oy)Ov0JT;?pdHEr8ms^l1+qCc5aK{CGP>6Q0G+2Ld;kCd literal 0 HcmV?d00001 From b36f05ae4132decd733212c0ca9c1ee5f35a18e2 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 19 Sep 2026 22:12:33 +0800 Subject: [PATCH 3/3] perf: skip unref for empty RAM decode frames --- cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp b/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp index 4ea6948..c6b2d0f 100644 --- a/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp +++ b/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp @@ -197,8 +197,9 @@ class FFmpegRamDecoder { LOG_ERROR(std::string("hw_frames_ctx is NULL")); goto _exit; } - if (sw_frame_->width != frame_->width || - sw_frame_->height != frame_->height) { + if ((sw_frame_->width != 0 || sw_frame_->height != 0) && + (sw_frame_->width != frame_->width || + sw_frame_->height != frame_->height)) { av_frame_unref(sw_frame_); } if ((ret = av_hwframe_transfer_data(sw_frame_, frame_, 0)) < 0) {