2024-10-23 14:48:03 +01:00
|
|
|
use std::mem::transmute;
|
|
|
|
use std::ptr;
|
|
|
|
|
2024-11-09 15:47:03 +00:00
|
|
|
use crate::{bail_ffmpeg, rstr};
|
2024-11-06 13:41:42 +00:00
|
|
|
use anyhow::{bail, Error};
|
2024-10-23 14:48:03 +01:00
|
|
|
use ffmpeg_sys_the_third::{
|
2024-11-06 14:12:45 +00:00
|
|
|
av_frame_alloc, av_frame_copy_props, av_get_pix_fmt_name, sws_freeContext, sws_getContext,
|
2024-11-06 13:41:42 +00:00
|
|
|
sws_scale_frame, AVFrame, AVPixelFormat, SwsContext, SWS_BILINEAR,
|
2024-10-23 14:48:03 +01:00
|
|
|
};
|
2024-11-06 14:12:45 +00:00
|
|
|
use log::trace;
|
2024-10-23 14:48:03 +01:00
|
|
|
|
|
|
|
pub struct Scaler {
|
|
|
|
width: u16,
|
|
|
|
height: u16,
|
|
|
|
format: AVPixelFormat,
|
|
|
|
ctx: *mut SwsContext,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Scaler {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
2024-11-09 15:47:03 +00:00
|
|
|
if !self.ctx.is_null() {
|
|
|
|
sws_freeContext(self.ctx);
|
|
|
|
}
|
2024-10-23 14:48:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-06 14:12:45 +00:00
|
|
|
impl Default for Scaler {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-23 14:48:03 +01:00
|
|
|
impl Scaler {
|
2024-11-06 14:12:45 +00:00
|
|
|
pub fn new() -> Self {
|
2024-10-23 14:48:03 +01:00
|
|
|
Self {
|
2024-11-06 14:12:45 +00:00
|
|
|
width: 0,
|
|
|
|
height: 0,
|
|
|
|
format: AVPixelFormat::AV_PIX_FMT_YUV420P,
|
2024-10-23 14:48:03 +01:00
|
|
|
ctx: ptr::null_mut(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn setup_scaler(
|
|
|
|
&mut self,
|
|
|
|
frame: *const AVFrame,
|
|
|
|
width: u16,
|
|
|
|
height: u16,
|
2024-11-06 13:41:42 +00:00
|
|
|
format: AVPixelFormat,
|
2024-10-23 14:48:03 +01:00
|
|
|
) -> Result<(), Error> {
|
2024-11-06 13:41:42 +00:00
|
|
|
if !self.ctx.is_null()
|
|
|
|
&& self.width == width
|
|
|
|
&& self.height == height
|
|
|
|
&& self.format == format
|
|
|
|
{
|
2024-10-23 14:48:03 +01:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear previous context, before re-creating
|
|
|
|
if !self.ctx.is_null() {
|
|
|
|
sws_freeContext(self.ctx);
|
|
|
|
self.ctx = ptr::null_mut();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.ctx = sws_getContext(
|
|
|
|
(*frame).width,
|
|
|
|
(*frame).height,
|
|
|
|
transmute((*frame).format),
|
2024-11-06 13:45:54 +00:00
|
|
|
width as libc::c_int,
|
|
|
|
height as libc::c_int,
|
|
|
|
transmute(format),
|
2024-10-23 14:48:03 +01:00
|
|
|
SWS_BILINEAR,
|
|
|
|
ptr::null_mut(),
|
|
|
|
ptr::null_mut(),
|
|
|
|
ptr::null_mut(),
|
|
|
|
);
|
|
|
|
if self.ctx.is_null() {
|
2024-11-06 13:41:42 +00:00
|
|
|
bail!("Failed to create scalar context");
|
2024-10-23 14:48:03 +01:00
|
|
|
}
|
2024-11-06 13:41:42 +00:00
|
|
|
|
2024-11-06 14:12:45 +00:00
|
|
|
trace!(
|
|
|
|
"scale setup: {}x{}@{} => {}x{}@{}",
|
|
|
|
(*frame).width,
|
|
|
|
(*frame).height,
|
|
|
|
rstr!(av_get_pix_fmt_name(transmute((*frame).format))),
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
rstr!(av_get_pix_fmt_name(format))
|
|
|
|
);
|
|
|
|
|
2024-11-06 13:41:42 +00:00
|
|
|
self.width = width;
|
|
|
|
self.height = height;
|
|
|
|
self.format = format;
|
2024-10-23 14:48:03 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn process_frame(
|
|
|
|
&mut self,
|
|
|
|
frame: *mut AVFrame,
|
|
|
|
width: u16,
|
|
|
|
height: u16,
|
2024-11-06 13:41:42 +00:00
|
|
|
format: AVPixelFormat,
|
2024-10-23 14:48:03 +01:00
|
|
|
) -> Result<*mut AVFrame, Error> {
|
2024-11-06 10:07:28 +00:00
|
|
|
if !(*frame).hw_frames_ctx.is_null() {
|
2024-11-06 13:41:42 +00:00
|
|
|
bail!("Hardware frames are not supported in this software scalar");
|
2024-11-06 10:07:28 +00:00
|
|
|
}
|
|
|
|
|
2024-11-06 13:41:42 +00:00
|
|
|
self.setup_scaler(frame, width, height, format)?;
|
2024-10-23 14:48:03 +01:00
|
|
|
|
|
|
|
let dst_frame = av_frame_alloc();
|
|
|
|
let ret = av_frame_copy_props(dst_frame, frame);
|
2024-11-09 15:47:03 +00:00
|
|
|
bail_ffmpeg!(ret);
|
2024-10-23 14:48:03 +01:00
|
|
|
|
|
|
|
let ret = sws_scale_frame(self.ctx, dst_frame, frame);
|
2024-11-09 15:47:03 +00:00
|
|
|
bail_ffmpeg!(ret);
|
2024-10-23 14:48:03 +01:00
|
|
|
|
|
|
|
Ok(dst_frame)
|
|
|
|
}
|
|
|
|
}
|
2024-11-06 14:12:45 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2024-11-08 14:28:38 +00:00
|
|
|
use crate::generate_test_frame;
|
|
|
|
use ffmpeg_sys_the_third::{av_frame_free, AVPixelFormat};
|
2024-11-06 14:12:45 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn scale_rgb24_yuv420() {
|
|
|
|
unsafe {
|
2024-11-08 14:28:38 +00:00
|
|
|
let mut frame = generate_test_frame();
|
2024-11-06 14:12:45 +00:00
|
|
|
let mut scaler = Scaler::new();
|
|
|
|
|
|
|
|
// downscale
|
|
|
|
let mut out_frame = scaler
|
|
|
|
.process_frame(frame, 128, 128, AVPixelFormat::AV_PIX_FMT_YUV420P)
|
|
|
|
.expect("Failed to process frame");
|
|
|
|
assert_eq!((*out_frame).width, 128);
|
|
|
|
assert_eq!((*out_frame).height, 128);
|
|
|
|
assert_eq!(
|
|
|
|
(*out_frame).format,
|
|
|
|
transmute(AVPixelFormat::AV_PIX_FMT_YUV420P)
|
|
|
|
);
|
|
|
|
av_frame_free(&mut out_frame);
|
|
|
|
|
|
|
|
// upscale
|
|
|
|
let mut out_frame = scaler
|
|
|
|
.process_frame(frame, 1024, 1024, AVPixelFormat::AV_PIX_FMT_YUV420P)
|
|
|
|
.expect("Failed to process frame");
|
|
|
|
assert_eq!((*out_frame).width, 1024);
|
|
|
|
assert_eq!((*out_frame).height, 1024);
|
|
|
|
assert_eq!(
|
|
|
|
(*out_frame).format,
|
|
|
|
transmute(AVPixelFormat::AV_PIX_FMT_YUV420P)
|
|
|
|
);
|
|
|
|
av_frame_free(&mut out_frame);
|
|
|
|
|
|
|
|
av_frame_free(&mut frame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|