This commit is contained in:
mofeng-git
2025-12-28 18:19:16 +08:00
commit d143d158e4
771 changed files with 220548 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
use super::Priority;
use crate::common::TEST_TIMEOUT_MS;
use crate::ffmpeg::{init_av_log, AVHWDeviceType::*};
use crate::{
common::DataFormat::*,
ffmpeg::{AVHWDeviceType, AVPixelFormat},
ffmpeg_ram::{
ffmpeg_ram_decode, ffmpeg_ram_free_decoder, ffmpeg_ram_new_decoder, CodecInfo,
AV_NUM_DATA_POINTERS,
},
};
use log::error;
use std::{
ffi::{c_void, CString},
os::raw::c_int,
slice::from_raw_parts,
time::Instant,
vec,
};
#[derive(Debug, Clone)]
pub struct DecodeContext {
pub name: String,
pub device_type: AVHWDeviceType,
pub thread_count: i32,
}
pub struct DecodeFrame {
pub pixfmt: AVPixelFormat,
pub width: i32,
pub height: i32,
pub data: Vec<Vec<u8>>,
pub linesize: Vec<i32>,
pub key: bool,
}
impl std::fmt::Display for DecodeFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = String::from("data:");
for data in self.data.iter() {
s.push_str(format!("{} ", data.len()).as_str());
}
s.push_str(", linesize:");
for linesize in self.linesize.iter() {
s.push_str(format!("{} ", linesize).as_str());
}
write!(
f,
"fixfmt:{}, width:{}, height:{},key:{}, {}",
self.pixfmt as i32, self.width, self.height, self.key, s,
)
}
}
pub struct Decoder {
codec: *mut c_void,
frames: *mut Vec<DecodeFrame>,
pub ctx: DecodeContext,
}
unsafe impl Send for Decoder {}
unsafe impl Sync for Decoder {}
impl Decoder {
pub fn new(ctx: DecodeContext) -> Result<Self, ()> {
init_av_log();
unsafe {
let codec = ffmpeg_ram_new_decoder(
CString::new(ctx.name.as_str()).map_err(|_| ())?.as_ptr(),
ctx.device_type as _,
ctx.thread_count,
Some(Decoder::callback),
);
if codec.is_null() {
return Err(());
}
Ok(Decoder {
codec,
frames: Box::into_raw(Box::new(Vec::<DecodeFrame>::new())),
ctx,
})
}
}
pub fn decode(&mut self, packet: &[u8]) -> Result<&mut Vec<DecodeFrame>, i32> {
unsafe {
(&mut *self.frames).clear();
let ret = ffmpeg_ram_decode(
self.codec,
packet.as_ptr(),
packet.len() as c_int,
self.frames as *const _ as *const c_void,
);
if ret < 0 {
Err(ret)
} else {
Ok(&mut *self.frames)
}
}
}
unsafe extern "C" fn callback(
obj: *const c_void,
width: c_int,
height: c_int,
pixfmt: c_int,
linesizes: *mut c_int,
datas: *mut *mut u8,
key: c_int,
) {
let frames = &mut *(obj as *mut Vec<DecodeFrame>);
let datas = from_raw_parts(datas, AV_NUM_DATA_POINTERS as _);
let linesizes = from_raw_parts(linesizes, AV_NUM_DATA_POINTERS as _);
let mut frame = DecodeFrame {
pixfmt: std::mem::transmute(pixfmt),
width,
height,
data: vec![],
linesize: vec![],
key: key != 0,
};
// Handle YUV420P and YUVJ420P (JPEG full-range) - same memory layout
if pixfmt == AVPixelFormat::AV_PIX_FMT_YUV420P as c_int
|| pixfmt == AVPixelFormat::AV_PIX_FMT_YUVJ420P as c_int
{
let y = from_raw_parts(datas[0], (linesizes[0] * height) as usize).to_vec();
let u = from_raw_parts(datas[1], (linesizes[1] * height / 2) as usize).to_vec();
let v = from_raw_parts(datas[2], (linesizes[2] * height / 2) as usize).to_vec();
frame.data.push(y);
frame.data.push(u);
frame.data.push(v);
frame.linesize.push(linesizes[0]);
frame.linesize.push(linesizes[1]);
frame.linesize.push(linesizes[2]);
frames.push(frame);
} else if pixfmt == AVPixelFormat::AV_PIX_FMT_YUV422P as c_int
|| pixfmt == AVPixelFormat::AV_PIX_FMT_YUVJ422P as c_int
{
// YUV422P: U and V planes have same height as Y (not half)
let y = from_raw_parts(datas[0], (linesizes[0] * height) as usize).to_vec();
let u = from_raw_parts(datas[1], (linesizes[1] * height) as usize).to_vec();
let v = from_raw_parts(datas[2], (linesizes[2] * height) as usize).to_vec();
frame.data.push(y);
frame.data.push(u);
frame.data.push(v);
frame.linesize.push(linesizes[0]);
frame.linesize.push(linesizes[1]);
frame.linesize.push(linesizes[2]);
frames.push(frame);
} else if pixfmt == AVPixelFormat::AV_PIX_FMT_NV12 as c_int
|| pixfmt == AVPixelFormat::AV_PIX_FMT_NV21 as c_int
{
let y = from_raw_parts(datas[0], (linesizes[0] * height) as usize).to_vec();
let uv = from_raw_parts(datas[1], (linesizes[1] * height / 2) as usize).to_vec();
frame.data.push(y);
frame.data.push(uv);
frame.linesize.push(linesizes[0]);
frame.linesize.push(linesizes[1]);
frames.push(frame);
} else {
error!("unsupported pixfmt {}", pixfmt as i32);
}
}
pub fn available_decoders() -> Vec<CodecInfo> {
use log::debug;
#[allow(unused_mut)]
let mut codecs: Vec<CodecInfo> = vec![];
// windows disable nvdec to avoid gpu stuck
#[cfg(target_os = "linux")]
{
let (nv, _, _) = crate::common::supported_gpu(false);
debug!("Linux GPU support detected - NV: {}", nv);
if nv {
codecs.push(CodecInfo {
name: "h264".to_owned(),
format: H264,
hwdevice: AV_HWDEVICE_TYPE_CUDA,
priority: Priority::Good as _,
..Default::default()
});
codecs.push(CodecInfo {
name: "hevc".to_owned(),
format: H265,
hwdevice: AV_HWDEVICE_TYPE_CUDA,
priority: Priority::Good as _,
..Default::default()
});
}
}
#[cfg(target_os = "windows")]
{
codecs.append(&mut vec![
CodecInfo {
name: "h264".to_owned(),
format: H264,
hwdevice: AV_HWDEVICE_TYPE_D3D11VA,
priority: Priority::Best as _,
..Default::default()
},
CodecInfo {
name: "hevc".to_owned(),
format: H265,
hwdevice: AV_HWDEVICE_TYPE_D3D11VA,
priority: Priority::Best as _,
..Default::default()
},
]);
}
#[cfg(target_os = "linux")]
{
codecs.append(&mut vec![
CodecInfo {
name: "h264".to_owned(),
format: H264,
hwdevice: AV_HWDEVICE_TYPE_VAAPI,
priority: Priority::Good as _,
..Default::default()
},
CodecInfo {
name: "hevc".to_owned(),
format: H265,
hwdevice: AV_HWDEVICE_TYPE_VAAPI,
priority: Priority::Good as _,
..Default::default()
},
CodecInfo {
name: "mjpeg".to_owned(),
format: MJPEG,
hwdevice: AV_HWDEVICE_TYPE_VAAPI,
priority: Priority::Good as _,
..Default::default()
},
]);
}
#[cfg(target_os = "macos")]
{
let (_, _, h264, h265) = crate::common::get_video_toolbox_codec_support();
debug!(
"VideoToolbox decode support - H264: {}, H265: {}",
h264, h265
);
if h264 {
codecs.push(CodecInfo {
name: "h264".to_owned(),
format: H264,
hwdevice: AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
priority: Priority::Best as _,
..Default::default()
});
}
if h265 {
codecs.push(CodecInfo {
name: "hevc".to_owned(),
format: H265,
hwdevice: AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
priority: Priority::Best as _,
..Default::default()
});
}
}
let mut res = Vec::<CodecInfo>::new();
let buf264 = &crate::common::DATA_H264_720P[..];
let buf265 = &crate::common::DATA_H265_720P[..];
for codec in codecs {
// Skip if this format already exists in results
if res
.iter()
.any(|existing: &CodecInfo| existing.format == codec.format)
{
continue;
}
debug!(
"Testing decoder: {} (hwdevice: {:?})",
codec.name, codec.hwdevice
);
let c = DecodeContext {
name: codec.name.clone(),
device_type: codec.hwdevice,
thread_count: 4,
};
match Decoder::new(c) {
Ok(mut decoder) => {
debug!("Decoder {} created successfully", codec.name);
// MJPEG decoder doesn't need test data - just verify it can be created
if codec.format == MJPEG {
debug!("MJPEG decoder {} test passed (creation only)", codec.name);
res.push(codec);
continue;
}
let data = match codec.format {
H264 => buf264,
H265 => buf265,
_ => {
log::error!("Unsupported format: {:?}, skipping", codec.format);
continue;
}
};
let start = Instant::now();
match decoder.decode(data) {
Ok(_) => {
let elapsed = start.elapsed().as_millis();
if elapsed < TEST_TIMEOUT_MS as _ {
debug!("Decoder {} test passed", codec.name);
res.push(codec);
} else {
debug!(
"Decoder {} test failed - timeout: {}ms",
codec.name, elapsed
);
}
}
Err(err) => {
debug!("Decoder {} test failed with error: {}", codec.name, err);
}
}
}
Err(_) => {
debug!("Failed to create decoder {}", codec.name);
}
}
}
let soft = CodecInfo::soft();
if let Some(c) = soft.h264 {
res.push(c);
}
if let Some(c) = soft.h265 {
res.push(c);
}
res
}
}
impl Drop for Decoder {
fn drop(&mut self) {
unsafe {
ffmpeg_ram_free_decoder(self.codec);
self.codec = std::ptr::null_mut();
let _ = Box::from_raw(self.frames);
}
}
}

View File

@@ -0,0 +1,523 @@
use crate::{
common::{
DataFormat::{self, *},
Quality, RateControl, TEST_TIMEOUT_MS,
},
ffmpeg::{init_av_log, AVPixelFormat},
ffmpeg_ram::{
ffmpeg_linesize_offset_length, ffmpeg_ram_encode, ffmpeg_ram_free_encoder,
ffmpeg_ram_new_encoder, ffmpeg_ram_request_keyframe, ffmpeg_ram_set_bitrate, CodecInfo, AV_NUM_DATA_POINTERS,
},
};
use log::trace;
use std::{
ffi::{c_void, CString},
fmt::Display,
os::raw::c_int,
slice,
};
use super::Priority;
#[cfg(any(windows, target_os = "linux"))]
use crate::common::Driver;
#[derive(Debug, Clone, PartialEq)]
pub struct EncodeContext {
pub name: String,
pub mc_name: Option<String>,
pub width: i32,
pub height: i32,
pub pixfmt: AVPixelFormat,
pub align: i32,
pub fps: i32,
pub gop: i32,
pub rc: RateControl,
pub quality: Quality,
pub kbs: i32,
pub q: i32,
pub thread_count: i32,
}
pub struct EncodeFrame {
pub data: Vec<u8>,
pub pts: i64,
pub key: i32,
}
impl Display for EncodeFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "encode len:{}, pts:{}", self.data.len(), self.pts)
}
}
pub struct Encoder {
codec: *mut c_void,
frames: *mut Vec<EncodeFrame>,
pub ctx: EncodeContext,
pub linesize: Vec<i32>,
pub offset: Vec<i32>,
pub length: i32,
}
impl Encoder {
pub fn new(ctx: EncodeContext) -> Result<Self, ()> {
init_av_log();
if ctx.width % 2 == 1 || ctx.height % 2 == 1 {
return Err(());
}
unsafe {
let mut linesize = Vec::<i32>::new();
linesize.resize(AV_NUM_DATA_POINTERS as _, 0);
let mut offset = Vec::<i32>::new();
offset.resize(AV_NUM_DATA_POINTERS as _, 0);
let mut length = Vec::<i32>::new();
length.resize(1, 0);
let gpu = std::env::var("RUSTDESK_HWCODEC_NVENC_GPU")
.unwrap_or("-1".to_owned())
.parse()
.unwrap_or(-1);
let mc_name = ctx.mc_name.clone().unwrap_or_default();
let codec = ffmpeg_ram_new_encoder(
CString::new(ctx.name.as_str()).map_err(|_| ())?.as_ptr(),
CString::new(mc_name.as_str()).map_err(|_| ())?.as_ptr(),
ctx.width,
ctx.height,
ctx.pixfmt as c_int,
ctx.align,
ctx.fps,
ctx.gop,
ctx.rc as _,
ctx.quality as _,
ctx.kbs,
ctx.q,
ctx.thread_count,
gpu,
linesize.as_mut_ptr(),
offset.as_mut_ptr(),
length.as_mut_ptr(),
Some(Encoder::callback),
);
if codec.is_null() {
return Err(());
}
Ok(Encoder {
codec,
frames: Box::into_raw(Box::new(Vec::<EncodeFrame>::new())),
ctx,
linesize,
offset,
length: length[0],
})
}
}
pub fn encode(&mut self, data: &[u8], ms: i64) -> Result<&mut Vec<EncodeFrame>, i32> {
unsafe {
(&mut *self.frames).clear();
let result = ffmpeg_ram_encode(
self.codec,
(*data).as_ptr(),
data.len() as _,
self.frames as *const _ as *const c_void,
ms,
);
if result != 0 {
return Err(result);
}
Ok(&mut *self.frames)
}
}
extern "C" fn callback(data: *const u8, size: c_int, pts: i64, key: i32, obj: *const c_void) {
unsafe {
let frames = &mut *(obj as *mut Vec<EncodeFrame>);
frames.push(EncodeFrame {
data: slice::from_raw_parts(data, size as _).to_vec(),
pts,
key,
});
}
}
pub fn set_bitrate(&mut self, kbs: i32) -> Result<(), ()> {
let ret = unsafe { ffmpeg_ram_set_bitrate(self.codec, kbs) };
if ret == 0 {
Ok(())
} else {
Err(())
}
}
/// Request next frame to be encoded as a keyframe (IDR)
pub fn request_keyframe(&mut self) {
unsafe {
ffmpeg_ram_request_keyframe(self.codec);
}
}
pub fn format_from_name(name: String) -> Result<DataFormat, ()> {
if name.contains("h264") {
return Ok(H264);
} else if name.contains("hevc") {
return Ok(H265);
} else if name.contains("vp8") {
return Ok(VP8);
} else if name.contains("vp9") {
return Ok(VP9);
} else if name.contains("av1") {
return Ok(AV1);
}
Err(())
}
pub fn available_encoders(ctx: EncodeContext, _sdk: Option<String>) -> Vec<CodecInfo> {
use log::debug;
if !(cfg!(windows) || cfg!(target_os = "linux") || cfg!(target_os = "macos")) {
return vec![];
}
let mut codecs: Vec<CodecInfo> = vec![];
#[cfg(any(windows, target_os = "linux"))]
{
let contains = |_vendor: Driver, _format: DataFormat| {
#[cfg(all(windows, feature = "vram"))]
{
if let Some(_sdk) = _sdk.as_ref() {
if !_sdk.is_empty() {
if let Ok(available) =
crate::vram::Available::deserialize(_sdk.as_str())
{
return available.contains(true, _vendor, _format);
}
}
}
}
true
};
let (_nv, amf, _intel) = crate::common::supported_gpu(true);
debug!(
"GPU support detected - NV: {}, AMF: {}, Intel: {}",
_nv, amf, _intel
);
#[cfg(windows)]
if _intel && contains(Driver::MFX, H264) {
codecs.push(CodecInfo {
name: "h264_qsv".to_owned(),
format: H264,
priority: Priority::Best as _,
..Default::default()
});
}
#[cfg(windows)]
if _intel && contains(Driver::MFX, H265) {
codecs.push(CodecInfo {
name: "hevc_qsv".to_owned(),
format: H265,
priority: Priority::Best as _,
..Default::default()
});
}
if _nv && contains(Driver::NV, H264) {
codecs.push(CodecInfo {
name: "h264_nvenc".to_owned(),
format: H264,
priority: Priority::Best as _,
..Default::default()
});
}
if _nv && contains(Driver::NV, H265) {
codecs.push(CodecInfo {
name: "hevc_nvenc".to_owned(),
format: H265,
priority: Priority::Best as _,
..Default::default()
});
}
if amf && contains(Driver::AMF, H264) {
codecs.push(CodecInfo {
name: "h264_amf".to_owned(),
format: H264,
priority: Priority::Best as _,
..Default::default()
});
}
if amf {
// sdk not use h265
codecs.push(CodecInfo {
name: "hevc_amf".to_owned(),
format: H265,
priority: Priority::Best as _,
..Default::default()
});
}
#[cfg(target_os = "linux")]
{
codecs.push(CodecInfo {
name: "h264_vaapi".to_owned(),
format: H264,
priority: Priority::Good as _,
..Default::default()
});
codecs.push(CodecInfo {
name: "hevc_vaapi".to_owned(),
format: H265,
priority: Priority::Good as _,
..Default::default()
});
codecs.push(CodecInfo {
name: "vp8_vaapi".to_owned(),
format: VP8,
priority: Priority::Good as _,
..Default::default()
});
codecs.push(CodecInfo {
name: "vp9_vaapi".to_owned(),
format: VP9,
priority: Priority::Good as _,
..Default::default()
});
// Rockchip MPP hardware encoder support
use std::ffi::c_int;
extern "C" {
fn linux_support_rkmpp() -> c_int;
fn linux_support_v4l2m2m() -> c_int;
}
if unsafe { linux_support_rkmpp() } == 0 {
debug!("RKMPP hardware detected, adding Rockchip encoders");
codecs.push(CodecInfo {
name: "h264_rkmpp".to_owned(),
format: H264,
priority: Priority::Best as _,
..Default::default()
});
codecs.push(CodecInfo {
name: "hevc_rkmpp".to_owned(),
format: H265,
priority: Priority::Best as _,
..Default::default()
});
}
// V4L2 Memory-to-Memory hardware encoder support (generic ARM)
if unsafe { linux_support_v4l2m2m() } == 0 {
debug!("V4L2 M2M hardware detected, adding V4L2 encoders");
codecs.push(CodecInfo {
name: "h264_v4l2m2m".to_owned(),
format: H264,
priority: Priority::Good as _,
..Default::default()
});
codecs.push(CodecInfo {
name: "hevc_v4l2m2m".to_owned(),
format: H265,
priority: Priority::Good as _,
..Default::default()
});
}
}
}
#[cfg(target_os = "macos")]
{
let (_h264, h265, _, _) = crate::common::get_video_toolbox_codec_support();
// h264 encode failed too often, not AV_CODEC_CAP_HARDWARE
// if h264 {
// codecs.push(CodecInfo {
// name: "h264_videotoolbox".to_owned(),
// format: H264,
// priority: Priority::Best as _,
// ..Default::default()
// });
// }
if h265 {
codecs.push(CodecInfo {
name: "hevc_videotoolbox".to_owned(),
format: H265,
priority: Priority::Best as _,
..Default::default()
});
}
}
// qsv doesn't support yuv420p
codecs.retain(|c| {
let ctx = ctx.clone();
if ctx.pixfmt == AVPixelFormat::AV_PIX_FMT_YUV420P && c.name.contains("qsv") {
return false;
}
return true;
});
let mut res = vec![];
if let Ok(yuv) = Encoder::dummy_yuv(ctx.clone()) {
for codec in codecs {
// Skip if this format already exists in results
if res
.iter()
.any(|existing: &CodecInfo| existing.format == codec.format)
{
continue;
}
debug!("Testing encoder: {}", codec.name);
let c = EncodeContext {
name: codec.name.clone(),
mc_name: codec.mc_name.clone(),
..ctx
};
match Encoder::new(c) {
Ok(mut encoder) => {
debug!("Encoder {} created successfully", codec.name);
let mut passed = false;
let mut last_err: Option<i32> = None;
let max_attempts = if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
3
} else {
1
};
for attempt in 0..max_attempts {
let pts = (attempt as i64) * 33; // 33ms is an approximation for 30 FPS (1000 / 30)
let start = std::time::Instant::now();
match encoder.encode(&yuv, pts) {
Ok(frames) => {
let elapsed = start.elapsed().as_millis();
if frames.len() == 1 {
if frames[0].key == 1 && elapsed < TEST_TIMEOUT_MS as _ {
debug!(
"Encoder {} test passed on attempt {}",
codec.name, attempt + 1
);
res.push(codec.clone());
passed = true;
break;
} else {
debug!(
"Encoder {} test failed on attempt {} - key: {}, timeout: {}ms",
codec.name,
attempt + 1,
frames[0].key,
elapsed
);
}
} else {
debug!(
"Encoder {} test failed on attempt {} - wrong frame count: {}",
codec.name,
attempt + 1,
frames.len()
);
}
}
Err(err) => {
last_err = Some(err);
debug!(
"Encoder {} test attempt {} returned error: {}",
codec.name,
attempt + 1,
err
);
}
}
}
if !passed {
debug!(
"Encoder {} test failed after retries{}",
codec.name,
last_err
.map(|e| format!(" (last err: {})", e))
.unwrap_or_default()
);
}
}
Err(_) => {
debug!("Failed to create encoder {}", codec.name);
}
}
}
} else {
debug!("Failed to generate dummy YUV data");
}
// Add software encoders as fallback
let soft_codecs = CodecInfo::soft();
// Add H264 software encoder if not already present
if !res.iter().any(|c| c.format == H264) {
if let Some(h264_soft) = soft_codecs.h264 {
debug!("Adding software H264 encoder: {}", h264_soft.name);
res.push(h264_soft);
}
}
// Add H265 software encoder if not already present
if !res.iter().any(|c| c.format == H265) {
if let Some(h265_soft) = soft_codecs.h265 {
debug!("Adding software H265 encoder: {}", h265_soft.name);
res.push(h265_soft);
}
}
// Add VP8 software encoder if not already present
if !res.iter().any(|c| c.format == VP8) {
if let Some(vp8_soft) = soft_codecs.vp8 {
debug!("Adding software VP8 encoder: {}", vp8_soft.name);
res.push(vp8_soft);
}
}
// Add VP9 software encoder if not already present
if !res.iter().any(|c| c.format == VP9) {
if let Some(vp9_soft) = soft_codecs.vp9 {
debug!("Adding software VP9 encoder: {}", vp9_soft.name);
res.push(vp9_soft);
}
}
// Add MJPEG software encoder if not already present
if !res.iter().any(|c| c.format == MJPEG) {
if let Some(mjpeg_soft) = soft_codecs.mjpeg {
debug!("Adding software MJPEG encoder: {}", mjpeg_soft.name);
res.push(mjpeg_soft);
}
}
res
}
fn dummy_yuv(ctx: EncodeContext) -> Result<Vec<u8>, ()> {
let mut yuv = vec![];
if let Ok((_, _, len)) = ffmpeg_linesize_offset_length(
ctx.pixfmt,
ctx.width as _,
ctx.height as _,
ctx.align as _,
) {
yuv.resize(len as _, 0);
return Ok(yuv);
}
Err(())
}
}
impl Drop for Encoder {
fn drop(&mut self) {
unsafe {
ffmpeg_ram_free_encoder(self.codec);
self.codec = std::ptr::null_mut();
let _ = Box::from_raw(self.frames);
trace!("Encoder dropped");
}
}
}

View File

@@ -0,0 +1,215 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::common::DataFormat::{self, *};
use crate::ffmpeg::{
AVHWDeviceType::{self, *},
AVPixelFormat,
};
use serde_derive::{Deserialize, Serialize};
use std::ffi::c_int;
include!(concat!(env!("OUT_DIR"), "/ffmpeg_ram_ffi.rs"));
pub mod decode;
pub mod encode;
pub enum Priority {
Best = 0,
Good = 1,
Normal = 2,
Soft = 3,
Bad = 4,
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct CodecInfo {
pub name: String,
#[serde(skip)]
pub mc_name: Option<String>,
pub format: DataFormat,
pub priority: i32,
pub hwdevice: AVHWDeviceType,
}
impl Default for CodecInfo {
fn default() -> Self {
Self {
name: Default::default(),
mc_name: Default::default(),
format: DataFormat::H264,
priority: Default::default(),
hwdevice: AVHWDeviceType::AV_HWDEVICE_TYPE_NONE,
}
}
}
impl CodecInfo {
pub fn prioritized(coders: Vec<CodecInfo>) -> CodecInfos {
let mut h264: Option<CodecInfo> = None;
let mut h265: Option<CodecInfo> = None;
let mut vp8: Option<CodecInfo> = None;
let mut vp9: Option<CodecInfo> = None;
let mut av1: Option<CodecInfo> = None;
let mut mjpeg: Option<CodecInfo> = None;
for coder in coders {
match coder.format {
DataFormat::H264 => match &h264 {
Some(old) => {
if old.priority > coder.priority {
h264 = Some(coder)
}
}
None => h264 = Some(coder),
},
DataFormat::H265 => match &h265 {
Some(old) => {
if old.priority > coder.priority {
h265 = Some(coder)
}
}
None => h265 = Some(coder),
},
DataFormat::VP8 => match &vp8 {
Some(old) => {
if old.priority > coder.priority {
vp8 = Some(coder)
}
}
None => vp8 = Some(coder),
},
DataFormat::VP9 => match &vp9 {
Some(old) => {
if old.priority > coder.priority {
vp9 = Some(coder)
}
}
None => vp9 = Some(coder),
},
DataFormat::AV1 => match &av1 {
Some(old) => {
if old.priority > coder.priority {
av1 = Some(coder)
}
}
None => av1 = Some(coder),
},
DataFormat::MJPEG => match &mjpeg {
Some(old) => {
if old.priority > coder.priority {
mjpeg = Some(coder)
}
}
None => mjpeg = Some(coder),
},
}
}
CodecInfos {
h264,
h265,
vp8,
vp9,
av1,
mjpeg,
}
}
pub fn soft() -> CodecInfos {
CodecInfos {
h264: Some(CodecInfo {
name: "h264".to_owned(),
mc_name: Default::default(),
format: H264,
hwdevice: AV_HWDEVICE_TYPE_NONE,
priority: Priority::Soft as _,
}),
h265: Some(CodecInfo {
name: "hevc".to_owned(),
mc_name: Default::default(),
format: H265,
hwdevice: AV_HWDEVICE_TYPE_NONE,
priority: Priority::Soft as _,
}),
vp8: Some(CodecInfo {
name: "libvpx".to_owned(),
mc_name: Default::default(),
format: VP8,
hwdevice: AV_HWDEVICE_TYPE_NONE,
priority: Priority::Soft as _,
}),
vp9: Some(CodecInfo {
name: "libvpx-vp9".to_owned(),
mc_name: Default::default(),
format: VP9,
hwdevice: AV_HWDEVICE_TYPE_NONE,
priority: Priority::Soft as _,
}),
av1: None,
mjpeg: Some(CodecInfo {
name: "mjpeg".to_owned(),
mc_name: Default::default(),
format: MJPEG,
hwdevice: AV_HWDEVICE_TYPE_NONE,
priority: Priority::Soft as _,
}),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct CodecInfos {
pub h264: Option<CodecInfo>,
pub h265: Option<CodecInfo>,
pub vp8: Option<CodecInfo>,
pub vp9: Option<CodecInfo>,
pub av1: Option<CodecInfo>,
pub mjpeg: Option<CodecInfo>,
}
impl CodecInfos {
pub fn serialize(&self) -> Result<String, ()> {
match serde_json::to_string_pretty(self) {
Ok(s) => Ok(s),
Err(_) => Err(()),
}
}
pub fn deserialize(s: &str) -> Result<Self, ()> {
match serde_json::from_str(s) {
Ok(c) => Ok(c),
Err(_) => Err(()),
}
}
}
pub fn ffmpeg_linesize_offset_length(
pixfmt: AVPixelFormat,
width: usize,
height: usize,
align: usize,
) -> Result<(Vec<i32>, Vec<i32>, i32), ()> {
let mut linesize = Vec::<c_int>::new();
linesize.resize(AV_NUM_DATA_POINTERS as _, 0);
let mut offset = Vec::<c_int>::new();
offset.resize(AV_NUM_DATA_POINTERS as _, 0);
let mut length = Vec::<c_int>::new();
length.resize(1, 0);
unsafe {
if ffmpeg_ram_get_linesize_offset_length(
pixfmt as _,
width as _,
height as _,
align as _,
linesize.as_mut_ptr(),
offset.as_mut_ptr(),
length.as_mut_ptr(),
) == 0
{
return Ok((linesize, offset, length[0]));
}
}
Err(())
}