feat: scale tests

This commit is contained in:
kieran 2024-11-06 14:12:45 +00:00
parent cf3ce4348e
commit f8a085af09
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
7 changed files with 110 additions and 70 deletions

View File

@ -1,8 +1,5 @@
use ffmpeg_rs_raw::{Decoder, Demuxer, DemuxerInfo, Filter, Scaler};
use ffmpeg_sys_the_third::AVHWDeviceType::{
AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D12VA, AV_HWDEVICE_TYPE_MEDIACODEC,
AV_HWDEVICE_TYPE_OPENCL, AV_HWDEVICE_TYPE_VDPAU, AV_HWDEVICE_TYPE_VULKAN,
};
use ffmpeg_rs_raw::{Decoder, Demuxer, DemuxerInfo, Filter};
use ffmpeg_sys_the_third::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA;
use ffmpeg_sys_the_third::{av_frame_free, av_packet_free, AVMediaType};
use log::{error, info};
use std::env::args;

View File

@ -1,4 +1,4 @@
use crate::{options_to_dict, return_ffmpeg_error, StreamInfoChannel};
use crate::{options_to_dict, return_ffmpeg_error, rstr, StreamInfoChannel};
use std::collections::{HashMap, HashSet};
use std::ffi::CStr;
use std::fmt::{Display, Formatter};
@ -46,9 +46,7 @@ impl Drop for DecoderCodecContext {
impl Display for DecoderCodecContext {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
unsafe {
let codec_name = CStr::from_ptr(avcodec_get_name((*self.codec).id))
.to_str()
.unwrap();
let codec_name = rstr!(avcodec_get_name((*self.codec).id));
write!(
f,
"DecoderCodecContext: codec={}, hw={}",
@ -56,9 +54,7 @@ impl Display for DecoderCodecContext {
if self.hw_config.is_null() {
"no"
} else {
CStr::from_ptr(av_hwdevice_get_type_name((*self.hw_config).device_type))
.to_str()
.unwrap()
rstr!(av_hwdevice_get_type_name((*self.hw_config).device_type))
}
)
}
@ -148,7 +144,7 @@ impl Decoder {
if codec.is_null() {
anyhow::bail!(
"Failed to find codec: {}",
CStr::from_ptr(avcodec_get_name((*codec_par).codec_id)).to_str()?
rstr!(avcodec_get_name((*codec_par).codec_id))
)
}
let context = avcodec_alloc_context3(codec);
@ -159,7 +155,7 @@ impl Decoder {
let mut ret = avcodec_parameters_to_context(context, (*stream).codecpar);
return_ffmpeg_error!(ret, "Failed to copy codec parameters to context");
let codec_name = CStr::from_ptr(avcodec_get_name((*codec).id)).to_str()?;
let codec_name = rstr!(avcodec_get_name((*codec).id));
// try use HW decoder
let mut hw_config = ptr::null();
if let Some(ref hw_types) = self.hw_decoder_types {
@ -171,9 +167,7 @@ impl Decoder {
if hw_config.is_null() {
break;
}
let hw_name =
CStr::from_ptr(av_hwdevice_get_type_name((*hw_config).device_type))
.to_str()?;
let hw_name = rstr!(av_hwdevice_get_type_name((*hw_config).device_type));
if !hw_types.contains(&(*hw_config).device_type) {
debug!("skipping hwaccel={}_{}", codec_name, hw_name);
continue;
@ -234,7 +228,7 @@ impl Decoder {
let mut pkgs = Vec::new();
while ret >= 0 {
let mut frame = av_frame_alloc();
let frame = av_frame_alloc();
ret = avcodec_receive_frame(ctx.context, frame);
if ret < 0 {
if ret == AVERROR_EOF || ret == AVERROR(libc::EAGAIN) {

View File

@ -1,4 +1,4 @@
use crate::return_ffmpeg_error;
use crate::{cstr, return_ffmpeg_error};
use crate::{get_ffmpeg_error_msg, DemuxerInfo, StreamChannelType, StreamInfoChannel};
use anyhow::Error;
use ffmpeg_sys_the_third::*;
@ -68,7 +68,7 @@ impl Demuxer {
match &mut self.input {
DemuxerInput::Url(input) => avformat_open_input(
&mut self.ctx,
format!("{}\0", input).as_ptr() as *const libc::c_char,
cstr!(input),
ptr::null_mut(),
ptr::null_mut(),
),
@ -89,7 +89,7 @@ impl Demuxer {
avformat_open_input(
&mut self.ctx,
if let Some(url) = url {
format!("{}\0", url).as_ptr() as *const libc::c_char
cstr!(url)
} else {
ptr::null_mut()
},

View File

@ -1,10 +1,9 @@
use crate::{cstr, return_ffmpeg_error, set_opts};
use crate::{cstr, return_ffmpeg_error, rstr, set_opts};
use anyhow::Error;
use ffmpeg_sys_the_third::{
av_free, av_strdup, avfilter_get_by_name, avfilter_graph_alloc, avfilter_graph_alloc_filter,
avfilter_graph_config, avfilter_graph_create_filter, avfilter_graph_dump, avfilter_graph_parse,
avfilter_graph_parse2, avfilter_graph_parse_ptr, avfilter_inout_alloc, AVFilterContext,
AVFilterGraph, AVFrame,
avfilter_inout_alloc, AVFilterContext, AVFilterGraph, AVFrame,
};
use log::debug;
use std::collections::HashMap;
@ -26,7 +25,7 @@ impl Filter {
///
/// https://ffmpeg.org/ffmpeg-filters.html
pub unsafe fn parse(graph: &str) -> Result<Self, Error> {
let mut ctx = avfilter_graph_alloc();
let ctx = avfilter_graph_alloc();
let inputs = avfilter_inout_alloc();
let outputs = avfilter_inout_alloc();
let src = avfilter_get_by_name(cstr!("buffer"));
@ -95,9 +94,8 @@ impl Filter {
}
pub unsafe fn build(&mut self) -> Result<(), Error> {
let mut d = avfilter_graph_dump(self.graph, ptr::null_mut());
debug!("{}", CStr::from_ptr(d).to_string_lossy());
av_free(d as *mut _);
let d = rstr!(avfilter_graph_dump(self.graph, ptr::null_mut()));
debug!("{}", d);
let ret = avfilter_graph_config(self.graph, ptr::null_mut());
return_ffmpeg_error!(ret, "Failed to build filter");

View File

@ -36,24 +36,26 @@ macro_rules! cstr {
};
}
#[macro_export]
macro_rules! rstr {
($str:expr) => {
core::ffi::CStr::from_ptr($str).to_str().unwrap()
};
}
fn get_ffmpeg_error_msg(ret: libc::c_int) -> String {
unsafe {
const BUF_SIZE: usize = 512;
let mut buf: [libc::c_char; BUF_SIZE] = [0; BUF_SIZE];
av_make_error_string(buf.as_mut_ptr(), BUF_SIZE, ret);
String::from(CStr::from_ptr(buf.as_ptr()).to_str().unwrap())
rstr!(buf.as_ptr()).to_string()
}
}
unsafe fn options_to_dict(options: HashMap<String, String>) -> Result<*mut AVDictionary, Error> {
let mut dict = ptr::null_mut();
for (key, value) in options {
let ret = av_dict_set(
&mut dict,
format!("{}\0", key).as_ptr() as *const libc::c_char,
format!("{}\0", value).as_ptr() as *const libc::c_char,
0,
);
let ret = av_dict_set(&mut dict, cstr!(key), cstr!(value), 0);
return_ffmpeg_error!(ret);
}
Ok(dict)
@ -93,11 +95,7 @@ fn list_opts(ctx: *mut libc::c_void) -> Result<Vec<String>, Error> {
break;
}
ret.push(
CStr::from_ptr((*opt_ptr).name)
.to_string_lossy()
.to_string(),
);
ret.push(rstr!((*opt_ptr).name).to_string());
}
}
Ok(ret)
@ -106,12 +104,7 @@ fn list_opts(ctx: *mut libc::c_void) -> Result<Vec<String>, Error> {
fn set_opts(ctx: *mut libc::c_void, options: HashMap<String, String>) -> Result<(), Error> {
unsafe {
for (key, value) in options {
let ret = av_opt_set(
ctx,
format!("{}\0", key).as_ptr() as *const libc::c_char,
format!("{}\0", value).as_ptr() as *const libc::c_char,
AV_OPT_SEARCH_CHILDREN,
);
let ret = av_opt_set(ctx, cstr!(key), cstr!(value), AV_OPT_SEARCH_CHILDREN);
return_ffmpeg_error!(ret);
}
}

View File

@ -1,12 +1,13 @@
use std::mem::transmute;
use std::ptr;
use crate::return_ffmpeg_error;
use crate::{return_ffmpeg_error, rstr};
use anyhow::{bail, Error};
use ffmpeg_sys_the_third::{
av_frame_alloc, av_frame_copy_props, sws_freeContext, sws_getContext, sws_init_context,
av_frame_alloc, av_frame_copy_props, av_get_pix_fmt_name, sws_freeContext, sws_getContext,
sws_scale_frame, AVFrame, AVPixelFormat, SwsContext, SWS_BILINEAR,
};
use log::trace;
pub struct Scaler {
width: u16,
@ -17,8 +18,6 @@ pub struct Scaler {
unsafe impl Send for Scaler {}
unsafe impl Sync for Scaler {}
impl Drop for Scaler {
fn drop(&mut self) {
unsafe {
@ -28,12 +27,18 @@ impl Drop for Scaler {
}
}
impl Default for Scaler {
fn default() -> Self {
Self::new()
}
}
impl Scaler {
pub fn new(width: u16, height: u16, format: AVPixelFormat) -> Self {
pub fn new() -> Self {
Self {
width,
height,
format,
width: 0,
height: 0,
format: AVPixelFormat::AV_PIX_FMT_YUV420P,
ctx: ptr::null_mut(),
}
}
@ -75,6 +80,16 @@ impl Scaler {
bail!("Failed to create scalar context");
}
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))
);
self.width = width;
self.height = height;
self.format = format;
@ -104,3 +119,54 @@ impl Scaler {
Ok(dst_frame)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ffmpeg_sys_the_third::{
av_frame_alloc, av_frame_free, av_frame_get_buffer, AVFrame, AVPixelFormat,
};
unsafe fn blank_frame() -> *mut AVFrame {
let frame = av_frame_alloc();
(*frame).width = 512;
(*frame).height = 512;
(*frame).format = AVPixelFormat::AV_PIX_FMT_RGB24 as libc::c_int;
av_frame_get_buffer(frame, 0);
frame
}
#[test]
fn scale_rgb24_yuv420() {
unsafe {
let mut frame = blank_frame();
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);
}
}
}

View File

@ -1,4 +1,4 @@
use crate::format_time;
use crate::{format_time, rstr};
use ffmpeg_sys_the_third::{
av_get_pix_fmt_name, av_get_sample_fmt_name, avcodec_get_name, AVMediaType, AVStream,
};
@ -130,44 +130,36 @@ impl StreamInfoChannel {
impl Display for StreamInfoChannel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let codec_name = unsafe { CStr::from_ptr(avcodec_get_name(transmute(self.codec as i32))) };
let codec_name = unsafe { rstr!(avcodec_get_name(transmute(self.codec as i32))) };
match self.channel_type {
StreamChannelType::Video => write!(
f,
"{} #{}: codec={},size={}x{},fps={:.3},pix_fmt={}",
self.channel_type,
self.index,
codec_name.to_str().unwrap(),
codec_name,
self.width,
self.height,
self.fps,
unsafe {
CStr::from_ptr(av_get_pix_fmt_name(transmute(self.format as libc::c_int)))
}
.to_str()
.unwrap(),
unsafe { rstr!(av_get_pix_fmt_name(transmute(self.format as libc::c_int))) },
),
StreamChannelType::Audio => write!(
f,
"{} #{}: codec={},format={},sample_rate={}",
self.channel_type,
self.index,
codec_name.to_str().unwrap(),
codec_name,
unsafe {
CStr::from_ptr(av_get_sample_fmt_name(transmute(
rstr!(av_get_sample_fmt_name(transmute(
self.format as libc::c_int,
)))
}
.to_str()
.unwrap(),
},
self.sample_rate,
),
StreamChannelType::Subtitle => write!(
f,
"{} #{}: codec={}",
self.channel_type,
self.index,
codec_name.to_str().unwrap()
self.channel_type, self.index, codec_name
),
}
}