refactor(hwcodec): 简化 hwcodec 库以适配 IP-KVM 场景

移除 IP-KVM 场景不需要的功能模块:
- 移除 VRAM 模块 (GPU 显存直接编解码)
- 移除 Mux 模块 (视频混流)
- 移除 macOS/Android 平台支持
- 移除外部 SDK 依赖 (~9MB)
- 移除开发工具和示例程序

简化解码器为仅支持 MJPEG (采集卡输出格式)
简化 NVIDIA 检测代码 (使用 dlopen 替代 SDK)
更新版本号至 0.8.0
更新相关技术文档
This commit is contained in:
mofeng-git
2025-12-31 19:47:08 +08:00
parent a8a3b6c66b
commit d0e2e13b35
441 changed files with 467 additions and 143421 deletions

View File

@@ -1,18 +0,0 @@
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 _,
);
}
}

View File

@@ -5,9 +5,6 @@
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,
@@ -31,14 +28,8 @@ pub(crate) fn supported_gpu(_encode: bool) -> (bool, bool, bool) {
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"))]
// Without VRAM feature, assume all GPU types might be available
// FFmpeg will handle the actual detection
return (true, true, true);
}
@@ -53,55 +44,21 @@ pub(crate) fn supported_gpu(_encode: bool) -> (bool, bool, bool) {
}
}
#[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"))]
#[cfg(windows)]
{
extern "C" {
pub fn GetHwcodecGpuSignature() -> u64;
}
unsafe { GetHwcodecGpuSignature() }
}
#[cfg(not(any(windows, target_os = "macos")))]
#[cfg(not(windows))]
{
0
}
}
// called by child process
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(target_os = "linux")]
pub fn setup_parent_death_signal() {
use std::sync::Once;
@@ -123,7 +80,6 @@ pub fn setup_parent_death_signal() {
});
}
// called by parent process
#[cfg(windows)]
pub fn child_exit_when_parent_exit(child_process_id: u32) -> bool {
unsafe {
@@ -133,4 +89,4 @@ pub fn child_exit_when_parent_exit(child_process_id: u32) -> bool {
let result = add_process_to_new_job(child_process_id);
result == 0
}
}
}

View File

@@ -1,6 +1,3 @@
#[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::{
@@ -8,7 +5,7 @@ use crate::{
ffmpeg::{AVHWDeviceType, AVPixelFormat},
ffmpeg_ram::{
ffmpeg_ram_decode, ffmpeg_ram_free_decoder, ffmpeg_ram_new_decoder, CodecInfo,
AV_NUM_DATA_POINTERS,
AV_NUM_DATA_POINTERS, Priority,
},
};
use log::error;
@@ -16,7 +13,6 @@ use std::{
ffi::{c_void, CString},
os::raw::c_int,
slice::from_raw_parts,
time::Instant,
vec,
};
@@ -179,188 +175,19 @@ impl Decoder {
}
}
/// Returns available decoders for IP-KVM scenario.
/// Only MJPEG software decoder is supported as IP-KVM captures from video capture cards
/// that output MJPEG streams.
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
// IP-KVM scenario only needs MJPEG decoding
// MJPEG comes from video capture cards, software decoding is sufficient
vec![CodecInfo {
name: "mjpeg".to_owned(),
format: MJPEG,
hwdevice: AV_HWDEVICE_TYPE_NONE,
priority: Priority::Best as _,
..Default::default()
}]
}
}

View File

@@ -1,8 +1,5 @@
use crate::{
common::{
DataFormat::{self, *},
Quality, RateControl, TEST_TIMEOUT_MS,
},
common::DataFormat::{self, *},
ffmpeg::{init_av_log, AVPixelFormat},
ffmpeg_ram::{
ffmpeg_linesize_offset_length, ffmpeg_ram_encode, ffmpeg_ram_free_encoder,
@@ -21,6 +18,9 @@ use super::Priority;
#[cfg(any(windows, target_os = "linux"))]
use crate::common::Driver;
/// Timeout for encoder test in milliseconds
const TEST_TIMEOUT_MS: u64 = 3000;
#[derive(Debug, Clone, PartialEq)]
pub struct EncodeContext {
pub name: String,
@@ -31,8 +31,8 @@ pub struct EncodeContext {
pub align: i32,
pub fps: i32,
pub gop: i32,
pub rc: RateControl,
pub quality: Quality,
pub rc: crate::common::RateControl,
pub quality: crate::common::Quality,
pub kbs: i32,
pub q: i32,
pub thread_count: i32,
@@ -175,25 +175,15 @@ impl Encoder {
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")) {
if !(cfg!(windows) || cfg!(target_os = "linux")) {
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);
}
}
}
}
// Without VRAM feature, we can't check SDK availability
// Just return true and let FFmpeg handle the actual detection
true
};
let (_nv, amf, _intel) = crate::common::supported_gpu(true);
@@ -245,7 +235,6 @@ impl Encoder {
});
}
if amf {
// sdk not use h265
codecs.push(CodecInfo {
name: "hevc_amf".to_owned(),
format: H265,
@@ -322,28 +311,6 @@ impl Encoder {
}
}
#[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();
@@ -379,11 +346,7 @@ impl Encoder {
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
};
let max_attempts = 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();

View File

@@ -1,11 +1,6 @@
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) {

View File

@@ -1,98 +0,0 @@
#![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");
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -1,62 +0,0 @@
#![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
}

View File

@@ -1,230 +0,0 @@
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
}

View File

@@ -1,248 +0,0 @@
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
}

View File

@@ -1,52 +0,0 @@
#![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
}

View File

@@ -1,88 +0,0 @@
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,
}

View File

@@ -1,58 +0,0 @@
#![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
}

View File

@@ -1,90 +0,0 @@
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)
}
}
}

View File

@@ -1,58 +0,0 @@
#![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
}