diff --git a/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp b/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp index fdd8a27..c6b2d0f 100644 --- a/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp +++ b/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp @@ -197,6 +197,11 @@ class FFmpegRamDecoder { LOG_ERROR(std::string("hw_frames_ctx is NULL")); goto _exit; } + 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) { LOG_ERROR(std::string("av_hwframe_transfer_data failed, ret = ") + av_err2str(ret)); 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 0000000..1164220 Binary files /dev/null and b/examples/res/decode_resolution/1080p.h264 differ diff --git a/examples/res/decode_resolution/1080p.h265 b/examples/res/decode_resolution/1080p.h265 new file mode 100644 index 0000000..27298d7 Binary files /dev/null and b/examples/res/decode_resolution/1080p.h265 differ diff --git a/examples/res/decode_resolution/360p.h264 b/examples/res/decode_resolution/360p.h264 new file mode 100644 index 0000000..3c616d9 Binary files /dev/null and b/examples/res/decode_resolution/360p.h264 differ diff --git a/examples/res/decode_resolution/360p.h265 b/examples/res/decode_resolution/360p.h265 new file mode 100644 index 0000000..486058a Binary files /dev/null and b/examples/res/decode_resolution/360p.h265 differ