From bc39a2ad99ad6489fa1525b9ad4fbca5dbf9cd07 Mon Sep 17 00:00:00 2001 From: kieran Date: Tue, 5 Nov 2024 10:59:12 +0000 Subject: [PATCH] feat: hardware decoding --- cashu.png | Bin 0 -> 862 bytes examples/cashu.svg | 66 ------------------------- examples/main.rs | 32 +++++------- src/decode.rs | 119 ++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 7 ++- 5 files changed, 125 insertions(+), 99 deletions(-) create mode 100644 cashu.png delete mode 100644 examples/cashu.svg diff --git a/cashu.png b/cashu.png new file mode 100644 index 0000000000000000000000000000000000000000..f545ca02fd6ae886b39700279273d3bb566dab28 GIT binary patch literal 862 zcmeAS@N?(olHy`uVBq!ia0vp^?La(}gAGWsT5CQ8Qk(@Ik;On71Q;1wD#d{uW&uwZ z$B>FSZ*Ljqi8zY5Uc9Nzoha(@rh#?B1i_7rK}Opg7-o73&U*e*uKfG|(9(5Zlv)mc z?HAZoyIc0kbpH2oeK*d(pWl8vFyA7#K!dG4357VmDA)4UcK`SMH~ZJix8J_h@Ss7N zja|mj;z7qjMt2@Qi==`Bg3Zi*5;EP73Mvr1f+IK;;*d(dcsQRE$vjkL$FuiFyo?I} zYq5HL>DB(tzo5+W|Cj!6|9<^^%&+s0v-dCm`26y&i*mw!+|q9iAMV%yVy^!6y*qZh zquh5%nO?j3g-y(TH#{ulYFAdDKi-t=40OEr{hZyVHFx&jH@W?JqyL|pV{5JZw%53q zo}bTrd@jTj8`s*uDX#vsqq_VF|0^5rr@gb)A^y*KI$tsN?{jB)>_Jj+BJ$6i8F0WEm+g@=KK8k_BRF&1z#j(3JwJBedT-@sHnXSsGOe{s8L=DXoQ^! z(6HJUngtA7p8a{v#12%+2qG>fTxD+psbm4E1gBO-SW_BO*0=wIv&P|uA^Ty>* yK8t%-rum;yv-vY^m3&!S==~2{$f0FRyZ3)(U#0x9tal$Mmw3ARxvX - - - - - - - - - - - diff --git a/examples/main.rs b/examples/main.rs index b0f6c64..2d12060 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -1,6 +1,6 @@ -use ffmpeg_rs_raw::{Decoder, Demuxer}; +use ffmpeg_rs_raw::{Decoder, Demuxer, DemuxerInfo}; +use ffmpeg_sys_the_third::AVHWDeviceType::{AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_VDPAU}; use ffmpeg_sys_the_third::{av_frame_free, av_packet_free}; -use std::collections::HashMap; use std::env::args; use std::fs::File; use std::io::{Cursor, Read}; @@ -20,20 +20,6 @@ fn main() { let fd = read_as_file(path.clone()); scan_input(fd); - - let mut opt = HashMap::new(); - opt.insert("analyzeduration".to_string(), "999".to_string()); - let svg = include_bytes!("./cashu.svg"); - let mut dx = Demuxer::new_custom_io(svg.as_slice(), Some("cashu.svg".to_string())); - dx.set_opt(opt.clone()).unwrap(); - unsafe { - let mut decoder = Decoder::new(); - let info = dx.probe_input().expect("probe failed"); - for chan in &info.channels { - decoder.setup_decoder(chan, None).expect("setup failed"); - } - loop_decoder(dx, decoder); - } } fn read_as_custom_io(path: PathBuf) -> Demuxer { @@ -51,12 +37,20 @@ fn scan_input(mut demuxer: Demuxer) { unsafe { let info = demuxer.probe_input().expect("demuxer failed"); println!("{}", info); - decode_input(demuxer); + decode_input(demuxer, info); } } -unsafe fn decode_input(demuxer: Demuxer) { - let decoder = Decoder::new(); +unsafe fn decode_input(demuxer: Demuxer, info: DemuxerInfo) { + let mut decoder = Decoder::new(); + decoder.enable_hw_decoder(AV_HWDEVICE_TYPE_VDPAU); + decoder.enable_hw_decoder(AV_HWDEVICE_TYPE_CUDA); + for ref stream in info.channels { + decoder + .setup_decoder(stream, None) + .expect("decoder setup failed"); + } + println!("{}", decoder); loop_decoder(demuxer, decoder); } diff --git a/src/decode.rs b/src/decode.rs index 2035265..e411d59 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -1,21 +1,25 @@ -use crate::{options_to_dict, StreamInfoChannel}; -use std::collections::HashMap; +use crate::{options_to_dict, return_ffmpeg_error, StreamInfoChannel}; +use std::collections::{HashMap, HashSet}; use std::ffi::CStr; +use std::fmt::{Display, Formatter}; use std::ptr; use anyhow::Error; use ffmpeg_sys_the_third::AVPictureType::AV_PICTURE_TYPE_NONE; use ffmpeg_sys_the_third::{ - av_buffer_alloc, av_frame_alloc, avcodec_alloc_context3, avcodec_find_decoder, - avcodec_free_context, avcodec_get_name, avcodec_open2, avcodec_parameters_to_context, - avcodec_receive_frame, avcodec_send_packet, AVCodec, AVCodecContext, AVFrame, AVMediaType, - AVPacket, AVStream, AVERROR, AVERROR_EOF, + av_buffer_alloc, av_buffer_ref, av_frame_alloc, av_frame_copy_props, av_frame_free, + av_hwdevice_ctx_create, av_hwdevice_get_type_name, av_hwframe_transfer_data, + avcodec_alloc_context3, avcodec_find_decoder, avcodec_free_context, avcodec_get_hw_config, + avcodec_get_name, avcodec_open2, avcodec_parameters_to_context, avcodec_receive_frame, + avcodec_send_packet, AVCodec, AVCodecContext, AVCodecHWConfig, AVFrame, AVHWDeviceType, + AVMediaType, AVPacket, AVStream, AVERROR, AVERROR_EOF, AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, }; use libc::memcpy; pub struct DecoderCodecContext { pub context: *mut AVCodecContext, pub codec: *const AVCodec, + pub hw_config: *const AVCodecHWConfig, } impl DecoderCodecContext { @@ -39,17 +43,61 @@ 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(); + write!( + f, + "DecoderCodecContext: codec={}, hw={}", + codec_name, + if self.hw_config.is_null() { + "no" + } else { + CStr::from_ptr(av_hwdevice_get_type_name((*self.hw_config).device_type)) + .to_str() + .unwrap() + } + ) + } + } +} + unsafe impl Send for DecoderCodecContext {} unsafe impl Sync for DecoderCodecContext {} pub struct Decoder { codecs: HashMap, + /// List of [AVHWDeviceType] which are enabled + hw_decoder_types: Option>, } +impl Display for Decoder { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for (idx, codec) in &self.codecs { + writeln!(f, "{}: {}", idx, codec)?; + } + Ok(()) + } +} impl Decoder { pub fn new() -> Self { Self { codecs: HashMap::new(), + hw_decoder_types: None, + } + } + + /// Enable hardware decoding with [hw_type] + pub fn enable_hw_decoder(&mut self, hw_type: AVHWDeviceType) { + if let Some(ref mut t) = self.hw_decoder_types { + t.insert(hw_type); + } else { + let mut hwt = HashSet::new(); + hwt.insert(hw_type); + self.hw_decoder_types = Some(hwt); } } @@ -91,8 +139,38 @@ impl Decoder { if context.is_null() { anyhow::bail!("Failed to alloc context") } - if avcodec_parameters_to_context(context, (*stream).codecpar) != 0 { - anyhow::bail!("Failed to copy codec parameters to context") + + let mut ret = avcodec_parameters_to_context(context, (*stream).codecpar); + return_ffmpeg_error!(ret, "Failed to copy codec parameters to context"); + + // try use HW decoder + let mut hw_config = ptr::null(); + if let Some(ref hw_types) = self.hw_decoder_types { + let mut hw_buf_ref = ptr::null_mut(); + let mut i = 0; + loop { + hw_config = avcodec_get_hw_config(codec, i); + i += 1; + if hw_config.is_null() { + break; + } + if !hw_types.contains(&(*hw_config).device_type) { + continue; + } + let hw_flag = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX as libc::c_int; + if (*hw_config).methods & hw_flag == hw_flag { + ret = av_hwdevice_ctx_create( + &mut hw_buf_ref, + (*hw_config).device_type, + ptr::null_mut(), + ptr::null_mut(), + 0, + ); + return_ffmpeg_error!(ret, "Failed to create HW ctx"); + (*context).hw_device_ctx = av_buffer_ref(hw_buf_ref); + break; + } + } } let mut dict = if let Some(options) = options { options_to_dict(options)? @@ -100,10 +178,14 @@ impl Decoder { ptr::null_mut() }; - if avcodec_open2(context, codec, &mut dict) < 0 { - anyhow::bail!("Failed to open codec") - } - Ok(e.insert(DecoderCodecContext { context, codec })) + ret = avcodec_open2(context, codec, &mut dict); + return_ffmpeg_error!(ret, "Failed to open codec"); + + Ok(e.insert(DecoderCodecContext { + context, + codec, + hw_config, + })) } else { anyhow::bail!("Decoder already setup"); } @@ -146,7 +228,7 @@ impl Decoder { let mut pkgs = Vec::new(); while ret >= 0 { - let frame = av_frame_alloc(); + let mut frame = av_frame_alloc(); ret = avcodec_receive_frame(ctx.context, frame); if ret < 0 { if ret == AVERROR_EOF || ret == AVERROR(libc::EAGAIN) { @@ -155,6 +237,17 @@ impl Decoder { return Err(Error::msg(format!("Failed to decode {}", ret))); } + // copy frame from GPU + if !ctx.hw_config.is_null() { + let sw_frame = av_frame_alloc(); + ret = av_hwframe_transfer_data(sw_frame, frame, 0); + return_ffmpeg_error!(ret, "Failed to transfer data from GPU"); + + av_frame_copy_props(sw_frame, frame); + av_frame_free(&mut frame); + frame = sw_frame; + } + (*frame).pict_type = AV_PICTURE_TYPE_NONE; // encoder prints warnings pkgs.push((frame, stream)); } diff --git a/src/lib.rs b/src/lib.rs index 755a798..13cf859 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,12 @@ mod stream_info; macro_rules! return_ffmpeg_error { ($x:expr) => { if $x < 0 { - return Err(Error::msg(get_ffmpeg_error_msg($x))); + anyhow::bail!(crate::get_ffmpeg_error_msg($x)) + } + }; + ($x:expr,$msg:expr) => { + if $x < 0 { + anyhow::bail!(format!("{}: {}", $msg, crate::get_ffmpeg_error_msg($x))) } }; }