mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-29 00:51:53 +08:00
init
This commit is contained in:
18
libs/hwcodec/src/android.rs
Normal file
18
libs/hwcodec/src/android.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use core::ffi::{c_int, c_void};
|
||||
|
||||
#[link(name = "avcodec")]
|
||||
extern "C" {
|
||||
fn av_jni_set_java_vm(
|
||||
vm: *mut c_void,
|
||||
ctx: *mut c_void,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
pub fn ffmpeg_set_java_vm(vm: *mut c_void) {
|
||||
unsafe {
|
||||
av_jni_set_java_vm(
|
||||
vm as _,
|
||||
std::ptr::null_mut() as _,
|
||||
);
|
||||
}
|
||||
}
|
||||
136
libs/hwcodec/src/common.rs
Normal file
136
libs/hwcodec/src/common.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
include!(concat!(env!("OUT_DIR"), "/common_ffi.rs"));
|
||||
|
||||
pub(crate) const DATA_H264_720P: &[u8] = include_bytes!("res/720p.h264");
|
||||
pub(crate) const DATA_H265_720P: &[u8] = include_bytes!("res/720p.h265");
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Driver {
|
||||
NV,
|
||||
AMF,
|
||||
MFX,
|
||||
FFMPEG,
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
pub(crate) fn supported_gpu(_encode: bool) -> (bool, bool, bool) {
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::ffi::c_int;
|
||||
#[cfg(target_os = "linux")]
|
||||
extern "C" {
|
||||
pub(crate) fn linux_support_nv() -> c_int;
|
||||
pub(crate) fn linux_support_amd() -> c_int;
|
||||
pub(crate) fn linux_support_intel() -> c_int;
|
||||
}
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
unsafe {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
#[cfg(feature = "vram")]
|
||||
return (
|
||||
_encode && crate::vram::nv::nv_encode_driver_support() == 0
|
||||
|| !_encode && crate::vram::nv::nv_decode_driver_support() == 0,
|
||||
crate::vram::amf::amf_driver_support() == 0,
|
||||
crate::vram::mfx::mfx_driver_support() == 0,
|
||||
);
|
||||
#[cfg(not(feature = "vram"))]
|
||||
return (true, true, true);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return (
|
||||
linux_support_nv() == 0,
|
||||
linux_support_amd() == 0,
|
||||
linux_support_intel() == 0,
|
||||
);
|
||||
#[allow(unreachable_code)]
|
||||
(false, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) fn get_video_toolbox_codec_support() -> (bool, bool, bool, bool) {
|
||||
use std::ffi::c_void;
|
||||
|
||||
extern "C" {
|
||||
fn checkVideoToolboxSupport(
|
||||
h264_encode: *mut i32,
|
||||
h265_encode: *mut i32,
|
||||
h264_decode: *mut i32,
|
||||
h265_decode: *mut i32,
|
||||
) -> c_void;
|
||||
}
|
||||
|
||||
let mut h264_encode = 0;
|
||||
let mut h265_encode = 0;
|
||||
let mut h264_decode = 0;
|
||||
let mut h265_decode = 0;
|
||||
unsafe {
|
||||
checkVideoToolboxSupport(
|
||||
&mut h264_encode as *mut _,
|
||||
&mut h265_encode as *mut _,
|
||||
&mut h264_decode as *mut _,
|
||||
&mut h265_decode as *mut _,
|
||||
);
|
||||
}
|
||||
(
|
||||
h264_encode == 1,
|
||||
h265_encode == 1,
|
||||
h264_decode == 1,
|
||||
h265_decode == 1,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_gpu_signature() -> u64 {
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
{
|
||||
extern "C" {
|
||||
pub fn GetHwcodecGpuSignature() -> u64;
|
||||
}
|
||||
unsafe { GetHwcodecGpuSignature() }
|
||||
}
|
||||
#[cfg(not(any(windows, target_os = "macos")))]
|
||||
{
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// called by child process
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn setup_parent_death_signal() {
|
||||
use std::sync::Once;
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| {
|
||||
use std::ffi::c_int;
|
||||
extern "C" {
|
||||
fn setup_parent_death_signal() -> c_int;
|
||||
}
|
||||
unsafe {
|
||||
let result = setup_parent_death_signal();
|
||||
if result == 0 {
|
||||
log::debug!("Successfully set up parent death signal");
|
||||
} else {
|
||||
log::warn!("Failed to set up parent death signal: {}", result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// called by parent process
|
||||
#[cfg(windows)]
|
||||
pub fn child_exit_when_parent_exit(child_process_id: u32) -> bool {
|
||||
unsafe {
|
||||
extern "C" {
|
||||
fn add_process_to_new_job(child_process_id: u32) -> i32;
|
||||
}
|
||||
let result = add_process_to_new_job(child_process_id);
|
||||
result == 0
|
||||
}
|
||||
}
|
||||
59
libs/hwcodec/src/ffmpeg.rs
Normal file
59
libs/hwcodec/src/ffmpeg.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/ffmpeg_ffi.rs"));
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum AVHWDeviceType {
|
||||
AV_HWDEVICE_TYPE_NONE,
|
||||
AV_HWDEVICE_TYPE_VDPAU,
|
||||
AV_HWDEVICE_TYPE_CUDA,
|
||||
AV_HWDEVICE_TYPE_VAAPI,
|
||||
AV_HWDEVICE_TYPE_DXVA2,
|
||||
AV_HWDEVICE_TYPE_QSV,
|
||||
AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_HWDEVICE_TYPE_DRM,
|
||||
AV_HWDEVICE_TYPE_OPENCL,
|
||||
AV_HWDEVICE_TYPE_MEDIACODEC,
|
||||
AV_HWDEVICE_TYPE_VULKAN,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn hwcodec_av_log_callback(level: i32, message: *const std::os::raw::c_char) {
|
||||
let could_not_find_ref_with_poc = "Could not find ref with POC";
|
||||
unsafe {
|
||||
let c_str = std::ffi::CStr::from_ptr(message);
|
||||
if let Ok(str_slice) = c_str.to_str() {
|
||||
let string = String::from(str_slice);
|
||||
if level == AV_LOG_ERROR as i32 {
|
||||
log::error!("{}", string);
|
||||
if string.contains(could_not_find_ref_with_poc) {
|
||||
hwcodec_set_flag_could_not_find_ref_with_poc();
|
||||
}
|
||||
} else if level == AV_LOG_PANIC as i32 || level == AV_LOG_FATAL as i32 {
|
||||
log::error!("{}", string);
|
||||
} else if level == AV_LOG_WARNING as i32 {
|
||||
log::warn!("{}", string);
|
||||
} else if level == AV_LOG_INFO as i32 {
|
||||
log::info!("{}", string);
|
||||
} else if level == AV_LOG_VERBOSE as i32 || level == AV_LOG_DEBUG as i32 {
|
||||
log::debug!("{}", string);
|
||||
} else if level == AV_LOG_TRACE as i32 {
|
||||
log::trace!("{}", string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn init_av_log() {
|
||||
static INIT: std::sync::Once = std::sync::Once::new();
|
||||
INIT.call_once(|| unsafe {
|
||||
av_log_set_level(AV_LOG_ERROR as i32);
|
||||
hwcodec_set_av_log_callback();
|
||||
});
|
||||
}
|
||||
375
libs/hwcodec/src/ffmpeg_ram/decode.rs
Normal file
375
libs/hwcodec/src/ffmpeg_ram/decode.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
523
libs/hwcodec/src/ffmpeg_ram/encode.rs
Normal file
523
libs/hwcodec/src/ffmpeg_ram/encode.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
215
libs/hwcodec/src/ffmpeg_ram/mod.rs
Normal file
215
libs/hwcodec/src/ffmpeg_ram/mod.rs
Normal 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(())
|
||||
}
|
||||
26
libs/hwcodec/src/lib.rs
Normal file
26
libs/hwcodec/src/lib.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
pub mod common;
|
||||
pub mod ffmpeg;
|
||||
pub mod ffmpeg_ram;
|
||||
pub mod mux;
|
||||
#[cfg(all(windows, feature = "vram"))]
|
||||
pub mod vram;
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod android;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn hwcodec_log(level: i32, message: *const std::os::raw::c_char) {
|
||||
unsafe {
|
||||
let c_str = std::ffi::CStr::from_ptr(message);
|
||||
if let Ok(str_slice) = c_str.to_str() {
|
||||
let string = String::from(str_slice);
|
||||
match level {
|
||||
0 => log::error!("{}", string),
|
||||
1 => log::warn!("{}", string),
|
||||
2 => log::info!("{}", string),
|
||||
3 => log::debug!("{}", string),
|
||||
4 => log::trace!("{}", string),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
libs/hwcodec/src/mux.rs
Normal file
98
libs/hwcodec/src/mux.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/mux_ffi.rs"));
|
||||
|
||||
use log::{error, trace};
|
||||
|
||||
use crate::ffmpeg::{av_log_get_level, AV_LOG_ERROR};
|
||||
use std::{
|
||||
ffi::{c_void, CString},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MuxContext {
|
||||
pub filename: String,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub is265: bool,
|
||||
pub framerate: usize,
|
||||
}
|
||||
|
||||
pub struct Muxer {
|
||||
inner: *mut c_void,
|
||||
pub ctx: MuxContext,
|
||||
start: Instant,
|
||||
}
|
||||
|
||||
unsafe impl Send for Muxer {}
|
||||
unsafe impl Sync for Muxer {}
|
||||
|
||||
impl Muxer {
|
||||
pub fn new(ctx: MuxContext) -> Result<Self, ()> {
|
||||
unsafe {
|
||||
let inner = hwcodec_new_muxer(
|
||||
CString::new(ctx.filename.as_str())
|
||||
.map_err(|_| ())?
|
||||
.as_ptr(),
|
||||
ctx.width as _,
|
||||
ctx.height as _,
|
||||
if ctx.is265 { 1 } else { 0 },
|
||||
ctx.framerate as _,
|
||||
);
|
||||
if inner.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(Muxer {
|
||||
inner,
|
||||
ctx,
|
||||
start: Instant::now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_video(&mut self, data: &[u8], key: bool) -> Result<(), i32> {
|
||||
unsafe {
|
||||
let result = hwcodec_write_video_frame(
|
||||
self.inner,
|
||||
(*data).as_ptr(),
|
||||
data.len() as _,
|
||||
self.start.elapsed().as_millis() as _,
|
||||
if key { 1 } else { 0 },
|
||||
);
|
||||
if result != 0 {
|
||||
if av_log_get_level() >= AV_LOG_ERROR as _ {
|
||||
error!("Error write_video: {}", result);
|
||||
}
|
||||
return Err(result);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_tail(&mut self) -> Result<(), i32> {
|
||||
unsafe {
|
||||
let result = hwcodec_write_tail(self.inner);
|
||||
if result != 0 {
|
||||
if av_log_get_level() >= AV_LOG_ERROR as _ {
|
||||
error!("Error write_tail: {}", result);
|
||||
}
|
||||
return Err(result);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Muxer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
hwcodec_free_muxer(self.inner);
|
||||
self.inner = std::ptr::null_mut();
|
||||
trace!("Muxer dropped");
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
libs/hwcodec/src/res/720p.h264
Normal file
BIN
libs/hwcodec/src/res/720p.h264
Normal file
Binary file not shown.
BIN
libs/hwcodec/src/res/720p.h265
Normal file
BIN
libs/hwcodec/src/res/720p.h265
Normal file
Binary file not shown.
62
libs/hwcodec/src/vram/amf.rs
Normal file
62
libs/hwcodec/src/vram/amf.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/amf_ffi.rs"));
|
||||
|
||||
use crate::{
|
||||
common::DataFormat::*,
|
||||
vram::inner::{DecodeCalls, EncodeCalls, InnerDecodeContext, InnerEncodeContext},
|
||||
};
|
||||
|
||||
pub fn encode_calls() -> EncodeCalls {
|
||||
EncodeCalls {
|
||||
new: amf_new_encoder,
|
||||
encode: amf_encode,
|
||||
destroy: amf_destroy_encoder,
|
||||
test: amf_test_encode,
|
||||
set_bitrate: amf_set_bitrate,
|
||||
set_framerate: amf_set_framerate,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_calls() -> DecodeCalls {
|
||||
DecodeCalls {
|
||||
new: amf_new_decoder,
|
||||
decode: amf_decode,
|
||||
destroy: amf_destroy_decoder,
|
||||
test: amf_test_decode,
|
||||
}
|
||||
}
|
||||
|
||||
// to-do: hardware ability
|
||||
pub fn possible_support_encoders() -> Vec<InnerEncodeContext> {
|
||||
if unsafe { amf_driver_support() } != 0 {
|
||||
return vec![];
|
||||
}
|
||||
let codecs = vec![H264, H265];
|
||||
|
||||
let mut v = vec![];
|
||||
for codec in codecs.iter() {
|
||||
v.push(InnerEncodeContext {
|
||||
format: codec.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn possible_support_decoders() -> Vec<InnerDecodeContext> {
|
||||
if unsafe { amf_driver_support() } != 0 {
|
||||
return vec![];
|
||||
}
|
||||
// https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/432#issuecomment-1873141122
|
||||
let codecs = vec![H264];
|
||||
|
||||
let mut v = vec![];
|
||||
for codec in codecs.iter() {
|
||||
v.push(InnerDecodeContext {
|
||||
data_format: codec.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
230
libs/hwcodec/src/vram/decode.rs
Normal file
230
libs/hwcodec/src/vram/decode.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
use crate::{
|
||||
common::{DataFormat::*, Driver::*},
|
||||
ffmpeg::init_av_log,
|
||||
vram::{amf, ffmpeg, inner::DecodeCalls, mfx, nv, DecodeContext},
|
||||
};
|
||||
use log::trace;
|
||||
use std::ffi::c_void;
|
||||
|
||||
pub struct Decoder {
|
||||
calls: DecodeCalls,
|
||||
codec: *mut c_void,
|
||||
frames: *mut Vec<DecodeFrame>,
|
||||
pub ctx: DecodeContext,
|
||||
}
|
||||
|
||||
unsafe impl Send for Decoder {}
|
||||
unsafe impl Sync for Decoder {}
|
||||
|
||||
extern "C" {
|
||||
fn hwcodec_get_d3d11_texture_width_height(
|
||||
texture: *mut c_void,
|
||||
width: *mut i32,
|
||||
height: *mut i32,
|
||||
);
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
pub fn new(ctx: DecodeContext) -> Result<Self, ()> {
|
||||
init_av_log();
|
||||
let calls = match ctx.driver {
|
||||
NV => nv::decode_calls(),
|
||||
AMF => amf::decode_calls(),
|
||||
MFX => mfx::decode_calls(),
|
||||
FFMPEG => ffmpeg::decode_calls(),
|
||||
};
|
||||
unsafe {
|
||||
let codec = (calls.new)(
|
||||
ctx.device.unwrap_or(std::ptr::null_mut()),
|
||||
ctx.luid,
|
||||
ctx.data_format as i32,
|
||||
);
|
||||
if codec.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
Ok(Self {
|
||||
calls,
|
||||
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 = (self.calls.decode)(
|
||||
self.codec,
|
||||
packet.as_ptr() as _,
|
||||
packet.len() as _,
|
||||
Some(Self::callback),
|
||||
self.frames as *mut _ as *mut c_void,
|
||||
);
|
||||
|
||||
if ret != 0 {
|
||||
Err(ret)
|
||||
} else {
|
||||
Ok(&mut *self.frames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn callback(texture: *mut c_void, obj: *const c_void) {
|
||||
let frames = &mut *(obj as *mut Vec<DecodeFrame>);
|
||||
let mut width = 0;
|
||||
let mut height = 0;
|
||||
hwcodec_get_d3d11_texture_width_height(texture, &mut width, &mut height);
|
||||
|
||||
let frame = DecodeFrame {
|
||||
texture,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
frames.push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Decoder {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.calls.destroy)(self.codec);
|
||||
self.codec = std::ptr::null_mut();
|
||||
let _ = Box::from_raw(self.frames);
|
||||
trace!("Decoder dropped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DecodeFrame {
|
||||
pub texture: *mut c_void,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
pub fn available() -> Vec<DecodeContext> {
|
||||
use log::debug;
|
||||
|
||||
let mut codecs: Vec<_> = vec![];
|
||||
// disable nv sdk decode
|
||||
// codecs.append(
|
||||
// &mut nv::possible_support_decoders()
|
||||
// .drain(..)
|
||||
// .map(|n| (NV, n))
|
||||
// .collect(),
|
||||
// );
|
||||
codecs.append(
|
||||
&mut ffmpeg::possible_support_decoders()
|
||||
.drain(..)
|
||||
.map(|n| (FFMPEG, n))
|
||||
.collect(),
|
||||
);
|
||||
codecs.append(
|
||||
&mut amf::possible_support_decoders()
|
||||
.drain(..)
|
||||
.map(|n| (AMF, n))
|
||||
.collect(),
|
||||
);
|
||||
codecs.append(
|
||||
&mut mfx::possible_support_decoders()
|
||||
.drain(..)
|
||||
.map(|n| (MFX, n))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let inputs: Vec<DecodeContext> = codecs
|
||||
.drain(..)
|
||||
.map(|(driver, n)| DecodeContext {
|
||||
device: None,
|
||||
driver: driver.clone(),
|
||||
vendor: driver, // Initially set vendor same as driver, will be updated by test results
|
||||
data_format: n.data_format,
|
||||
luid: 0,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut outputs = Vec::<DecodeContext>::new();
|
||||
let mut exclude_luid_formats = Vec::<(i64, i32)>::new();
|
||||
let buf264 = &crate::common::DATA_H264_720P[..];
|
||||
let buf265 = &crate::common::DATA_H265_720P[..];
|
||||
|
||||
for input in inputs {
|
||||
debug!(
|
||||
"Testing vram decoder: driver={:?}, format={:?}",
|
||||
input.driver, input.data_format
|
||||
);
|
||||
|
||||
let test = match input.driver {
|
||||
NV => nv::decode_calls().test,
|
||||
AMF => amf::decode_calls().test,
|
||||
MFX => mfx::decode_calls().test,
|
||||
FFMPEG => ffmpeg::decode_calls().test,
|
||||
};
|
||||
|
||||
let mut luids: Vec<i64> = vec![0; crate::vram::MAX_ADATERS];
|
||||
let mut vendors: Vec<i32> = vec![0; crate::vram::MAX_ADATERS];
|
||||
let mut desc_count: i32 = 0;
|
||||
|
||||
let data = match input.data_format {
|
||||
H264 => buf264,
|
||||
H265 => buf265,
|
||||
_ => {
|
||||
debug!("Unsupported data format: {:?}, skipping", input.data_format);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let (excluded_luids, exclude_formats): (Vec<i64>, Vec<i32>) = exclude_luid_formats
|
||||
.iter()
|
||||
.map(|(luid, format)| (*luid, *format))
|
||||
.unzip();
|
||||
|
||||
let result = unsafe {
|
||||
test(
|
||||
luids.as_mut_ptr(),
|
||||
vendors.as_mut_ptr(),
|
||||
luids.len() as _,
|
||||
&mut desc_count,
|
||||
input.data_format as i32,
|
||||
data.as_ptr() as *mut u8,
|
||||
data.len() as _,
|
||||
excluded_luids.as_ptr(),
|
||||
exclude_formats.as_ptr(),
|
||||
exclude_luid_formats.len() as i32,
|
||||
)
|
||||
};
|
||||
|
||||
if result == 0 {
|
||||
if desc_count as usize <= luids.len() {
|
||||
debug!(
|
||||
"vram decoder test passed: driver={:?}, adapters={}",
|
||||
input.driver, desc_count
|
||||
);
|
||||
for i in 0..desc_count as usize {
|
||||
let mut input = input.clone();
|
||||
input.luid = luids[i];
|
||||
input.vendor = match vendors[i] {
|
||||
0 => NV,
|
||||
1 => AMF,
|
||||
2 => MFX,
|
||||
_ => {
|
||||
log::error!(
|
||||
"Unexpected vendor value encountered: {}. Skipping.",
|
||||
vendors[i]
|
||||
);
|
||||
continue;
|
||||
}, };
|
||||
exclude_luid_formats.push((luids[i], input.data_format as i32));
|
||||
outputs.push(input);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"vram decoder test failed: driver={:?}, error={}",
|
||||
input.driver, result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
outputs
|
||||
}
|
||||
248
libs/hwcodec/src/vram/encode.rs
Normal file
248
libs/hwcodec/src/vram/encode.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
use crate::{
|
||||
common::Driver::*,
|
||||
ffmpeg::init_av_log,
|
||||
vram::{
|
||||
amf, ffmpeg, inner::EncodeCalls, mfx, nv, DynamicContext, EncodeContext, FeatureContext,
|
||||
},
|
||||
};
|
||||
use log::trace;
|
||||
use std::{
|
||||
fmt::Display, os::raw::{c_int, c_void}, slice::from_raw_parts
|
||||
};
|
||||
|
||||
pub struct Encoder {
|
||||
calls: EncodeCalls,
|
||||
codec: *mut c_void,
|
||||
frames: *mut Vec<EncodeFrame>,
|
||||
pub ctx: EncodeContext,
|
||||
}
|
||||
|
||||
unsafe impl Send for Encoder {}
|
||||
unsafe impl Sync for Encoder {}
|
||||
|
||||
impl Encoder {
|
||||
pub fn new(ctx: EncodeContext) -> Result<Self, ()> {
|
||||
init_av_log();
|
||||
if ctx.d.width % 2 == 1 || ctx.d.height % 2 == 1 {
|
||||
return Err(());
|
||||
}
|
||||
let calls = match ctx.f.driver {
|
||||
NV => nv::encode_calls(),
|
||||
AMF => amf::encode_calls(),
|
||||
MFX => mfx::encode_calls(),
|
||||
FFMPEG => ffmpeg::encode_calls(),
|
||||
};
|
||||
unsafe {
|
||||
let codec = (calls.new)(
|
||||
ctx.d.device.unwrap_or(std::ptr::null_mut()),
|
||||
ctx.f.luid,
|
||||
ctx.f.data_format as i32,
|
||||
ctx.d.width,
|
||||
ctx.d.height,
|
||||
ctx.d.kbitrate,
|
||||
ctx.d.framerate,
|
||||
ctx.d.gop,
|
||||
);
|
||||
if codec.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
Ok(Self {
|
||||
calls,
|
||||
codec,
|
||||
frames: Box::into_raw(Box::new(Vec::<EncodeFrame>::new())),
|
||||
ctx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(&mut self, tex: *mut c_void, ms: i64) -> Result<&mut Vec<EncodeFrame>, i32> {
|
||||
unsafe {
|
||||
(&mut *self.frames).clear();
|
||||
let result = (self.calls.encode)(
|
||||
self.codec,
|
||||
tex,
|
||||
Some(Self::callback),
|
||||
self.frames as *mut _ as *mut c_void,
|
||||
ms,
|
||||
);
|
||||
if result != 0 {
|
||||
Err(result)
|
||||
} else {
|
||||
Ok(&mut *self.frames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn callback(data: *const u8, size: c_int, key: i32, obj: *const c_void, pts: i64) {
|
||||
unsafe {
|
||||
let frames = &mut *(obj as *mut Vec<EncodeFrame>);
|
||||
frames.push(EncodeFrame {
|
||||
data: from_raw_parts(data, size as usize).to_vec(),
|
||||
pts,
|
||||
key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_bitrate(&mut self, kbs: i32) -> Result<(), i32> {
|
||||
unsafe {
|
||||
match (self.calls.set_bitrate)(self.codec, kbs) {
|
||||
0 => Ok(()),
|
||||
err => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_framerate(&mut self, framerate: i32) -> Result<(), i32> {
|
||||
unsafe {
|
||||
match (self.calls.set_framerate)(self.codec, framerate) {
|
||||
0 => Ok(()),
|
||||
err => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Encoder {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.calls.destroy)(self.codec);
|
||||
self.codec = std::ptr::null_mut();
|
||||
let _ = Box::from_raw(self.frames);
|
||||
trace!("Encoder dropped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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:{}, key:{}", self.data.len(), self.key)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available(d: DynamicContext) -> Vec<FeatureContext> {
|
||||
use log::debug;
|
||||
|
||||
let mut natives: Vec<_> = vec![];
|
||||
natives.append(
|
||||
&mut ffmpeg::possible_support_encoders()
|
||||
.drain(..)
|
||||
.map(|n| (FFMPEG, n))
|
||||
.collect(),
|
||||
);
|
||||
natives.append(
|
||||
&mut nv::possible_support_encoders()
|
||||
.drain(..)
|
||||
.map(|n| (NV, n))
|
||||
.collect(),
|
||||
);
|
||||
natives.append(
|
||||
&mut amf::possible_support_encoders()
|
||||
.drain(..)
|
||||
.map(|n| (AMF, n))
|
||||
.collect(),
|
||||
);
|
||||
natives.append(
|
||||
&mut mfx::possible_support_encoders()
|
||||
.drain(..)
|
||||
.map(|n| (MFX, n))
|
||||
.collect(),
|
||||
);
|
||||
let inputs: Vec<EncodeContext> = natives
|
||||
.drain(..)
|
||||
.map(|(driver, n)| EncodeContext {
|
||||
f: FeatureContext {
|
||||
driver: driver.clone(),
|
||||
vendor: driver, // Initially set vendor same as driver, will be updated by test results
|
||||
data_format: n.format,
|
||||
luid: 0,
|
||||
},
|
||||
d,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut outputs = Vec::<EncodeContext>::new();
|
||||
let mut exclude_luid_formats = Vec::<(i64, i32)>::new();
|
||||
|
||||
for input in inputs {
|
||||
debug!(
|
||||
"Testing vram encoder: driver={:?}, format={:?}",
|
||||
input.f.driver, input.f.data_format
|
||||
);
|
||||
|
||||
let test = match input.f.driver {
|
||||
NV => nv::encode_calls().test,
|
||||
AMF => amf::encode_calls().test,
|
||||
MFX => mfx::encode_calls().test,
|
||||
FFMPEG => ffmpeg::encode_calls().test,
|
||||
};
|
||||
|
||||
let mut luids: Vec<i64> = vec![0; crate::vram::MAX_ADATERS];
|
||||
let mut vendors: Vec<i32> = vec![0; crate::vram::MAX_ADATERS];
|
||||
let mut desc_count: i32 = 0;
|
||||
|
||||
let (excluded_luids, exclude_formats): (Vec<i64>, Vec<i32>) = exclude_luid_formats
|
||||
.iter()
|
||||
.map(|(luid, format)| (*luid, *format))
|
||||
.unzip();
|
||||
|
||||
let result = unsafe {
|
||||
test(
|
||||
luids.as_mut_ptr(),
|
||||
vendors.as_mut_ptr(),
|
||||
luids.len() as _,
|
||||
&mut desc_count,
|
||||
input.f.data_format as i32,
|
||||
input.d.width,
|
||||
input.d.height,
|
||||
input.d.kbitrate,
|
||||
input.d.framerate,
|
||||
input.d.gop,
|
||||
excluded_luids.as_ptr(),
|
||||
exclude_formats.as_ptr(),
|
||||
exclude_luid_formats.len() as i32,
|
||||
)
|
||||
};
|
||||
|
||||
if result == 0 {
|
||||
if desc_count as usize <= luids.len() {
|
||||
debug!(
|
||||
"vram encoder test passed: driver={:?}, adapters={}",
|
||||
input.f.driver, desc_count
|
||||
);
|
||||
for i in 0..desc_count as usize {
|
||||
let mut input = input.clone();
|
||||
input.f.luid = luids[i];
|
||||
input.f.vendor = match vendors[i] {
|
||||
0 => NV,
|
||||
1 => AMF,
|
||||
2 => MFX,
|
||||
_ => {
|
||||
log::error!(
|
||||
"Unexpected vendor value encountered: {}. Skipping.",
|
||||
vendors[i]
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
exclude_luid_formats.push((luids[i], input.f.data_format as i32));
|
||||
outputs.push(input);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"vram encoder test failed: driver={:?}, error={}",
|
||||
input.f.driver, result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result: Vec<_> = outputs.drain(..).map(|e| e.f).collect();
|
||||
result
|
||||
}
|
||||
52
libs/hwcodec/src/vram/ffmpeg.rs
Normal file
52
libs/hwcodec/src/vram/ffmpeg.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/ffmpeg_vram_ffi.rs"));
|
||||
|
||||
use crate::{
|
||||
common::DataFormat::*,
|
||||
vram::inner::{DecodeCalls, EncodeCalls, InnerDecodeContext, InnerEncodeContext},
|
||||
};
|
||||
|
||||
pub fn encode_calls() -> EncodeCalls {
|
||||
EncodeCalls {
|
||||
new: ffmpeg_vram_new_encoder,
|
||||
encode: ffmpeg_vram_encode,
|
||||
destroy: ffmpeg_vram_destroy_encoder,
|
||||
test: ffmpeg_vram_test_encode,
|
||||
set_bitrate: ffmpeg_vram_set_bitrate,
|
||||
set_framerate: ffmpeg_vram_set_framerate,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_calls() -> DecodeCalls {
|
||||
DecodeCalls {
|
||||
new: ffmpeg_vram_new_decoder,
|
||||
decode: ffmpeg_vram_decode,
|
||||
destroy: ffmpeg_vram_destroy_decoder,
|
||||
test: ffmpeg_vram_test_decode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn possible_support_encoders() -> Vec<InnerEncodeContext> {
|
||||
let dataFormats = vec![H264, H265];
|
||||
let mut v = vec![];
|
||||
for dataFormat in dataFormats.iter() {
|
||||
v.push(InnerEncodeContext {
|
||||
format: dataFormat.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn possible_support_decoders() -> Vec<InnerDecodeContext> {
|
||||
let codecs = vec![H264, H265];
|
||||
let mut v = vec![];
|
||||
for codec in codecs.iter() {
|
||||
v.push(InnerDecodeContext {
|
||||
data_format: codec.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
88
libs/hwcodec/src/vram/inner.rs
Normal file
88
libs/hwcodec/src/vram/inner.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use crate::common::{DataFormat, DecodeCallback, EncodeCallback};
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
pub type NewEncoderCall = unsafe extern "C" fn(
|
||||
hdl: *mut c_void,
|
||||
luid: i64,
|
||||
codecID: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
bitrate: i32,
|
||||
framerate: i32,
|
||||
gop: i32,
|
||||
) -> *mut c_void;
|
||||
|
||||
pub type EncodeCall = unsafe extern "C" fn(
|
||||
encoder: *mut c_void,
|
||||
tex: *mut c_void,
|
||||
callback: EncodeCallback,
|
||||
obj: *mut c_void,
|
||||
ms: i64,
|
||||
) -> c_int;
|
||||
|
||||
pub type NewDecoderCall =
|
||||
unsafe extern "C" fn(device: *mut c_void, luid: i64, dataFormat: i32) -> *mut c_void;
|
||||
|
||||
pub type DecodeCall = unsafe extern "C" fn(
|
||||
decoder: *mut c_void,
|
||||
data: *mut u8,
|
||||
length: i32,
|
||||
callback: DecodeCallback,
|
||||
obj: *mut c_void,
|
||||
) -> c_int;
|
||||
|
||||
pub type TestEncodeCall = unsafe extern "C" fn(
|
||||
outLuids: *mut i64,
|
||||
outVendors: *mut i32,
|
||||
maxDescNum: i32,
|
||||
outDescNum: *mut i32,
|
||||
dataFormat: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
kbs: i32,
|
||||
framerate: i32,
|
||||
gop: i32,
|
||||
excludedLuids: *const i64,
|
||||
excludeFormats: *const i32,
|
||||
excludeCount: i32,
|
||||
) -> c_int;
|
||||
|
||||
pub type TestDecodeCall = unsafe extern "C" fn(
|
||||
outLuids: *mut i64,
|
||||
outVendors: *mut i32,
|
||||
maxDescNum: i32,
|
||||
outDescNum: *mut i32,
|
||||
dataFormat: i32,
|
||||
data: *mut u8,
|
||||
length: i32,
|
||||
excludedLuids: *const i64,
|
||||
excludeFormats: *const i32,
|
||||
excludeCount: i32,
|
||||
) -> c_int;
|
||||
|
||||
pub type IVCall = unsafe extern "C" fn(v: *mut c_void) -> c_int;
|
||||
|
||||
pub type IVICall = unsafe extern "C" fn(v: *mut c_void, i: i32) -> c_int;
|
||||
|
||||
pub struct EncodeCalls {
|
||||
pub new: NewEncoderCall,
|
||||
pub encode: EncodeCall,
|
||||
pub destroy: IVCall,
|
||||
pub test: TestEncodeCall,
|
||||
pub set_bitrate: IVICall,
|
||||
pub set_framerate: IVICall,
|
||||
}
|
||||
pub struct DecodeCalls {
|
||||
pub new: NewDecoderCall,
|
||||
pub decode: DecodeCall,
|
||||
pub destroy: IVCall,
|
||||
pub test: TestDecodeCall,
|
||||
}
|
||||
|
||||
pub struct InnerEncodeContext {
|
||||
pub format: DataFormat,
|
||||
}
|
||||
|
||||
pub struct InnerDecodeContext {
|
||||
pub data_format: DataFormat,
|
||||
}
|
||||
58
libs/hwcodec/src/vram/mfx.rs
Normal file
58
libs/hwcodec/src/vram/mfx.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/mfx_ffi.rs"));
|
||||
|
||||
use crate::{
|
||||
common::DataFormat::*,
|
||||
vram::inner::{DecodeCalls, EncodeCalls, InnerDecodeContext, InnerEncodeContext},
|
||||
};
|
||||
|
||||
pub fn encode_calls() -> EncodeCalls {
|
||||
EncodeCalls {
|
||||
new: mfx_new_encoder,
|
||||
encode: mfx_encode,
|
||||
destroy: mfx_destroy_encoder,
|
||||
test: mfx_test_encode,
|
||||
set_bitrate: mfx_set_bitrate,
|
||||
set_framerate: mfx_set_framerate,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_calls() -> DecodeCalls {
|
||||
DecodeCalls {
|
||||
new: mfx_new_decoder,
|
||||
decode: mfx_decode,
|
||||
destroy: mfx_destroy_decoder,
|
||||
test: mfx_test_decode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn possible_support_encoders() -> Vec<InnerEncodeContext> {
|
||||
if unsafe { mfx_driver_support() } != 0 {
|
||||
return vec![];
|
||||
}
|
||||
let dataFormats = vec![H264, H265];
|
||||
let mut v = vec![];
|
||||
for dataFormat in dataFormats.iter() {
|
||||
v.push(InnerEncodeContext {
|
||||
format: dataFormat.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn possible_support_decoders() -> Vec<InnerDecodeContext> {
|
||||
if unsafe { mfx_driver_support() } != 0 {
|
||||
return vec![];
|
||||
}
|
||||
let dataFormats = vec![H264, H265];
|
||||
let mut v = vec![];
|
||||
for dataFormat in dataFormats.iter() {
|
||||
v.push(InnerDecodeContext {
|
||||
data_format: dataFormat.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
90
libs/hwcodec/src/vram/mod.rs
Normal file
90
libs/hwcodec/src/vram/mod.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
pub(crate) mod amf;
|
||||
pub mod decode;
|
||||
pub mod encode;
|
||||
pub(crate) mod ffmpeg;
|
||||
mod inner;
|
||||
pub(crate) mod mfx;
|
||||
pub(crate) mod nv;
|
||||
|
||||
pub(crate) const MAX_ADATERS: usize = 16;
|
||||
|
||||
use crate::common::{DataFormat, Driver};
|
||||
pub use serde;
|
||||
pub use serde_derive;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::ffi::c_void;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct FeatureContext {
|
||||
pub driver: Driver,
|
||||
pub vendor: Driver,
|
||||
pub luid: i64,
|
||||
pub data_format: DataFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct DynamicContext {
|
||||
#[serde(skip)]
|
||||
pub device: Option<*mut c_void>,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub kbitrate: i32,
|
||||
pub framerate: i32,
|
||||
pub gop: i32,
|
||||
}
|
||||
|
||||
unsafe impl Send for DynamicContext {}
|
||||
unsafe impl Sync for DynamicContext {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct EncodeContext {
|
||||
pub f: FeatureContext,
|
||||
pub d: DynamicContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct DecodeContext {
|
||||
#[serde(skip)]
|
||||
pub device: Option<*mut c_void>,
|
||||
pub driver: Driver,
|
||||
pub vendor: Driver,
|
||||
pub luid: i64,
|
||||
pub data_format: DataFormat,
|
||||
}
|
||||
|
||||
unsafe impl Send for DecodeContext {}
|
||||
unsafe impl Sync for DecodeContext {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct Available {
|
||||
pub e: Vec<FeatureContext>,
|
||||
pub d: Vec<DecodeContext>,
|
||||
}
|
||||
|
||||
impl Available {
|
||||
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 contains(&self, encode: bool, vendor: Driver, data_format: DataFormat) -> bool {
|
||||
if encode {
|
||||
self.e
|
||||
.iter()
|
||||
.any(|f| f.vendor == vendor && f.data_format == data_format)
|
||||
} else {
|
||||
self.d
|
||||
.iter()
|
||||
.any(|d| d.vendor == vendor && d.data_format == data_format)
|
||||
}
|
||||
}
|
||||
}
|
||||
58
libs/hwcodec/src/vram/nv.rs
Normal file
58
libs/hwcodec/src/vram/nv.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/nv_ffi.rs"));
|
||||
|
||||
use crate::{
|
||||
common::DataFormat::*,
|
||||
vram::inner::{DecodeCalls, EncodeCalls, InnerDecodeContext, InnerEncodeContext},
|
||||
};
|
||||
|
||||
pub fn encode_calls() -> EncodeCalls {
|
||||
EncodeCalls {
|
||||
new: nv_new_encoder,
|
||||
encode: nv_encode,
|
||||
destroy: nv_destroy_encoder,
|
||||
test: nv_test_encode,
|
||||
set_bitrate: nv_set_bitrate,
|
||||
set_framerate: nv_set_framerate,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode_calls() -> DecodeCalls {
|
||||
DecodeCalls {
|
||||
new: nv_new_decoder,
|
||||
decode: nv_decode,
|
||||
destroy: nv_destroy_decoder,
|
||||
test: nv_test_decode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn possible_support_encoders() -> Vec<InnerEncodeContext> {
|
||||
if unsafe { nv_encode_driver_support() } != 0 {
|
||||
return vec![];
|
||||
}
|
||||
let dataFormats = vec![H264, H265];
|
||||
let mut v = vec![];
|
||||
for dataFormat in dataFormats.iter() {
|
||||
v.push(InnerEncodeContext {
|
||||
format: dataFormat.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn possible_support_decoders() -> Vec<InnerDecodeContext> {
|
||||
if unsafe { nv_encode_driver_support() } != 0 {
|
||||
return vec![];
|
||||
}
|
||||
let dataFormats = vec![H264, H265];
|
||||
let mut v = vec![];
|
||||
for dataFormat in dataFormats.iter() {
|
||||
v.push(InnerDecodeContext {
|
||||
data_format: dataFormat.clone(),
|
||||
});
|
||||
}
|
||||
v
|
||||
}
|
||||
Reference in New Issue
Block a user