mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
feat: 新增安卓平台支持
This commit is contained in:
@@ -9,14 +9,16 @@ ignored = ["serde"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
bytes = ["dep:bytes"]
|
||||
rkmpp = []
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
bytes = { version = "1", optional = true }
|
||||
serde_derive = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
bindgen = "0.59"
|
||||
bindgen = "0.70.1"
|
||||
|
||||
@@ -21,11 +21,16 @@ fn build_common(builder: &mut Build) {
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let common_dir = manifest_dir.join("cpp").join("common");
|
||||
|
||||
bindgen::builder()
|
||||
let mut bindings = bindgen::builder()
|
||||
.header(common_dir.join("common.h").to_string_lossy().to_string())
|
||||
.header(common_dir.join("callback.h").to_string_lossy().to_string())
|
||||
.rustified_enum("*")
|
||||
.parse_callbacks(Box::new(CommonCallbacks))
|
||||
.rustified_enum(".*")
|
||||
.parse_callbacks(Box::new(CommonCallbacks));
|
||||
if target_os == "android" {
|
||||
print_android_bindgen_env();
|
||||
bindings = bindings.clang_args(android_clang_args());
|
||||
}
|
||||
bindings
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file(Path::new(&env::var_os("OUT_DIR").unwrap()).join("common_ffi.rs"))
|
||||
@@ -57,9 +62,9 @@ fn build_common(builder: &mut Build) {
|
||||
}
|
||||
|
||||
// Unsupported platforms
|
||||
if target_os != "windows" && target_os != "linux" {
|
||||
if target_os != "windows" && target_os != "linux" && target_os != "android" {
|
||||
panic!(
|
||||
"Unsupported OS: {}. Only Windows and Linux are supported.",
|
||||
"Unsupported OS: {}. Only Windows, Linux, and Android are supported.",
|
||||
target_os
|
||||
);
|
||||
}
|
||||
@@ -71,9 +76,9 @@ fn build_common(builder: &mut Build) {
|
||||
#[derive(Debug)]
|
||||
struct CommonCallbacks;
|
||||
impl bindgen::callbacks::ParseCallbacks for CommonCallbacks {
|
||||
fn add_derives(&self, name: &str) -> Vec<String> {
|
||||
fn add_derives(&self, info: &bindgen::callbacks::DeriveInfo<'_>) -> Vec<String> {
|
||||
let names = vec!["DataFormat", "SurfaceFormat", "API"];
|
||||
if names.contains(&name) {
|
||||
if names.contains(&info.name) {
|
||||
vec!["Serialize", "Deserialize"]
|
||||
.drain(..)
|
||||
.map(|s| s.to_string())
|
||||
@@ -84,12 +89,123 @@ impl bindgen::callbacks::ParseCallbacks for CommonCallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_android_bindgen_env() {
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME");
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_NDK_ROOT");
|
||||
println!("cargo:rerun-if-env-changed=NDK_HOME");
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_HOME");
|
||||
println!("cargo:rerun-if-env-changed=ANDROID_SDK_ROOT");
|
||||
println!("cargo:rerun-if-env-changed=CARGO_NDK_PLATFORM");
|
||||
}
|
||||
|
||||
fn android_clang_args() -> Vec<String> {
|
||||
let ndk = android_ndk_home();
|
||||
let target = env::var("TARGET").unwrap_or_default();
|
||||
let toolchain = ndk.join("toolchains/llvm/prebuilt").join(host_tag());
|
||||
let sysroot = toolchain.join("sysroot");
|
||||
let clang_include = toolchain
|
||||
.join("lib/clang")
|
||||
.join(clang_version(&toolchain))
|
||||
.join("include");
|
||||
let api = env::var("CARGO_NDK_PLATFORM")
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<u32>().ok())
|
||||
.unwrap_or(21);
|
||||
let clang_target = android_clang_target(&target);
|
||||
|
||||
vec![
|
||||
format!("--target={clang_target}"),
|
||||
format!("--sysroot={}", sysroot.display()),
|
||||
format!("-D__ANDROID_API__={api}"),
|
||||
format!("-isystem{}", clang_include.display()),
|
||||
format!("-isystem{}", sysroot.join("usr/include").display()),
|
||||
format!(
|
||||
"-isystem{}",
|
||||
sysroot.join("usr/include").join(clang_target).display()
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn android_clang_target(target: &str) -> &'static str {
|
||||
match target {
|
||||
"aarch64-linux-android" => "aarch64-linux-android",
|
||||
"armv7-linux-androideabi" => "armv7a-linux-androideabi",
|
||||
"i686-linux-android" => "i686-linux-android",
|
||||
"x86_64-linux-android" => "x86_64-linux-android",
|
||||
other => panic!("unsupported Android target for hwcodec bindgen: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn android_ndk_home() -> PathBuf {
|
||||
for key in ["ANDROID_NDK_HOME", "ANDROID_NDK_ROOT", "NDK_HOME"] {
|
||||
if let Ok(value) = env::var(key) {
|
||||
return PathBuf::from(value);
|
||||
}
|
||||
}
|
||||
|
||||
for key in ["ANDROID_HOME", "ANDROID_SDK_ROOT"] {
|
||||
if let Ok(value) = env::var(key) {
|
||||
let ndk_dir = PathBuf::from(value).join("ndk");
|
||||
if let Some(newest) = newest_child_dir(&ndk_dir) {
|
||||
return newest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!(
|
||||
"hwcodec Android bindgen requires ANDROID_NDK_HOME, ANDROID_NDK_ROOT, NDK_HOME, \
|
||||
or ANDROID_HOME/ANDROID_SDK_ROOT with an ndk directory"
|
||||
);
|
||||
}
|
||||
|
||||
fn newest_child_dir(path: &Path) -> Option<PathBuf> {
|
||||
let mut entries = std::fs::read_dir(path)
|
||||
.ok()?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path())
|
||||
.filter(|path| path.is_dir())
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort();
|
||||
entries.pop()
|
||||
}
|
||||
|
||||
fn host_tag() -> &'static str {
|
||||
if cfg!(target_os = "linux") {
|
||||
"linux-x86_64"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"darwin-x86_64"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows-x86_64"
|
||||
} else {
|
||||
panic!("unsupported host OS for Android NDK");
|
||||
}
|
||||
}
|
||||
|
||||
fn clang_version(toolchain: &Path) -> String {
|
||||
let clang_dir = toolchain.join("lib/clang");
|
||||
let mut entries = std::fs::read_dir(&clang_dir)
|
||||
.unwrap_or_else(|_| panic!("missing NDK clang directory: {}", clang_dir.display()))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.file_name().to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort();
|
||||
entries
|
||||
.pop()
|
||||
.unwrap_or_else(|| panic!("no clang versions found under: {}", clang_dir.display()))
|
||||
}
|
||||
|
||||
mod ffmpeg {
|
||||
use super::*;
|
||||
|
||||
pub fn build_ffmpeg(builder: &mut Build) {
|
||||
ffmpeg_ffi();
|
||||
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("android") {
|
||||
link_android_ffmpeg(builder);
|
||||
build_ffmpeg_ram(builder);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try VCPKG first, fallback to system FFmpeg via pkg-config
|
||||
if let Some(vcpkg_installed) = vcpkg_installed_root() {
|
||||
link_vcpkg(builder, vcpkg_installed);
|
||||
@@ -104,6 +220,67 @@ mod ffmpeg {
|
||||
build_ffmpeg_capture(builder);
|
||||
}
|
||||
|
||||
fn link_android_ffmpeg(builder: &mut Build) {
|
||||
let root = std::env::var("ONE_KVM_ANDROID_FFMPEG_ROOT").unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"ONE_KVM_ANDROID_FFMPEG_ROOT is required when building hwcodec for Android. \
|
||||
It must point to an FFmpeg Android build with MediaCodec enabled."
|
||||
)
|
||||
});
|
||||
let root = PathBuf::from(root);
|
||||
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
let abi = match target_arch.as_str() {
|
||||
"aarch64" => "arm64-v8a",
|
||||
"arm" => "armeabi-v7a",
|
||||
"x86" => "x86",
|
||||
"x86_64" => "x86_64",
|
||||
_ => target_arch.as_str(),
|
||||
};
|
||||
|
||||
let abi_root = root.join(abi);
|
||||
let lib_dir = if abi_root.join("lib").exists() {
|
||||
abi_root.join("lib")
|
||||
} else {
|
||||
root.join("lib")
|
||||
};
|
||||
let include_dir = if abi_root.join("include").exists() {
|
||||
abi_root.join("include")
|
||||
} else {
|
||||
root.join("include")
|
||||
};
|
||||
|
||||
if !include_dir.exists() || !lib_dir.exists() {
|
||||
panic!(
|
||||
"Invalid ONE_KVM_ANDROID_FFMPEG_ROOT: include/lib not found for ABI {} under {}",
|
||||
abi,
|
||||
root.display()
|
||||
);
|
||||
}
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", lib_dir.display());
|
||||
builder.include(&include_dir);
|
||||
|
||||
let use_static = std::env::var("ONE_KVM_ANDROID_FFMPEG_STATIC")
|
||||
.map(|value| value != "0")
|
||||
.unwrap_or(true);
|
||||
for lib in ["avcodec", "avutil"] {
|
||||
if use_static {
|
||||
println!("cargo:rustc-link-lib=static={}", lib);
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib={}", lib);
|
||||
}
|
||||
}
|
||||
|
||||
println!("cargo:rustc-link-lib=log");
|
||||
println!("cargo:rustc-link-lib=mediandk");
|
||||
println!("cargo:rustc-link-lib=android");
|
||||
println!("cargo:rustc-link-lib=dl");
|
||||
println!("cargo:rustc-link-lib=m");
|
||||
println!("cargo:rustc-link-lib=z");
|
||||
println!("cargo:rustc-link-lib=c++_shared");
|
||||
println!("cargo:info=Using Android FFmpeg from {}", root.display());
|
||||
}
|
||||
|
||||
fn vcpkg_installed_root() -> Option<PathBuf> {
|
||||
println!("cargo:rerun-if-env-changed=VCPKG_INSTALLED_DIR");
|
||||
println!("cargo:rerun-if-env-changed=VCPKG_ROOT");
|
||||
@@ -335,7 +512,10 @@ mod ffmpeg {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("cargo:warning=Windows QSV support library not found in {}", lib_dir.display());
|
||||
println!(
|
||||
"cargo:warning=Windows QSV support library not found in {}",
|
||||
lib_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
fn link_os() {
|
||||
@@ -358,9 +538,11 @@ mod ffmpeg {
|
||||
}
|
||||
// ARM (aarch64, arm): no X11 needed, uses RKMPP/V4L2
|
||||
v
|
||||
} else if target_os == "android" {
|
||||
Vec::new()
|
||||
} else {
|
||||
panic!(
|
||||
"Unsupported OS: {}. Only Windows and Linux are supported.",
|
||||
"Unsupported OS: {}. Only Windows, Linux, and Android are supported.",
|
||||
target_os
|
||||
);
|
||||
};
|
||||
@@ -376,9 +558,14 @@ mod ffmpeg {
|
||||
let ffi_header_path = ffmpeg_ram_dir.join("ffmpeg_ffi.h");
|
||||
println!("cargo:rerun-if-changed={}", ffi_header_path.display());
|
||||
let ffi_header = ffi_header_path.to_string_lossy().to_string();
|
||||
bindgen::builder()
|
||||
let mut bindings = bindgen::builder()
|
||||
.header(ffi_header)
|
||||
.rustified_enum("*")
|
||||
.rustified_enum(".*");
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("android") {
|
||||
print_android_bindgen_env();
|
||||
bindings = bindings.clang_args(android_clang_args());
|
||||
}
|
||||
bindings
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file(Path::new(&env::var_os("OUT_DIR").unwrap()).join("ffmpeg_ffi.rs"))
|
||||
@@ -392,9 +579,14 @@ mod ffmpeg {
|
||||
.join("ffmpeg_ram_ffi.h")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
bindgen::builder()
|
||||
let mut bindings = bindgen::builder()
|
||||
.header(ffi_header)
|
||||
.rustified_enum("*")
|
||||
.rustified_enum(".*");
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("android") {
|
||||
print_android_bindgen_env();
|
||||
bindings = bindings.clang_args(android_clang_args());
|
||||
}
|
||||
bindings
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file(Path::new(&env::var_os("OUT_DIR").unwrap()).join("ffmpeg_ram_ffi.rs"))
|
||||
@@ -405,7 +597,9 @@ mod ffmpeg {
|
||||
// RKMPP decode only exists on ARM builds where FFmpeg is compiled with RKMPP support.
|
||||
// Avoid compiling this file on x86/x64 where `AV_HWDEVICE_TYPE_RKMPP` doesn't exist.
|
||||
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
let enable_rkmpp = matches!(target_arch.as_str(), "aarch64" | "arm")
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
let enable_rkmpp = target_os != "android"
|
||||
&& matches!(target_arch.as_str(), "aarch64" | "arm")
|
||||
|| std::env::var_os("CARGO_FEATURE_RKMPP").is_some();
|
||||
if enable_rkmpp {
|
||||
builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_decode.cpp"));
|
||||
@@ -431,7 +625,7 @@ mod ffmpeg {
|
||||
.to_string();
|
||||
bindgen::builder()
|
||||
.header(capture_header)
|
||||
.rustified_enum("*")
|
||||
.rustified_enum(".*")
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file(
|
||||
@@ -454,14 +648,16 @@ mod ffmpeg {
|
||||
.to_string();
|
||||
bindgen::builder()
|
||||
.header(ffi_header)
|
||||
.rustified_enum("*")
|
||||
.rustified_enum(".*")
|
||||
.generate()
|
||||
.unwrap()
|
||||
.write_to_file(Path::new(&env::var_os("OUT_DIR").unwrap()).join("ffmpeg_hw_ffi.rs"))
|
||||
.unwrap();
|
||||
|
||||
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
let enable_rkmpp = matches!(target_arch.as_str(), "aarch64" | "arm")
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
||||
let enable_rkmpp = target_os != "android"
|
||||
&& matches!(target_arch.as_str(), "aarch64" | "arm")
|
||||
|| std::env::var_os("CARGO_FEATURE_RKMPP").is_some();
|
||||
if enable_rkmpp {
|
||||
// Include RGA headers for NV16->NV12 conversion (RGA im2d API)
|
||||
|
||||
@@ -100,8 +100,13 @@ void set_av_codec_ctx(AVCodecContext *c, const std::string &name, int kbs,
|
||||
c->color_primaries = AVCOL_PRI_SMPTE170M;
|
||||
c->color_trc = AVCOL_TRC_SMPTE170M;
|
||||
|
||||
// WebRTC SDP advertises constrained baseline. Keep hardware and software
|
||||
// encoders on the same browser-friendly H264 profile.
|
||||
// WebRTC SDP advertises constrained baseline. Keep most hardware and software
|
||||
// encoders on the same browser-friendly H264 profile. Android MediaCodec is
|
||||
// deliberately excluded because older vendor OMX encoders can reject explicit
|
||||
// profile/level combinations during configure().
|
||||
if (name.find("mediacodec") != std::string::npos) {
|
||||
return;
|
||||
}
|
||||
if (name.find("h264") != std::string::npos) {
|
||||
c->profile = AV_PROFILE_H264_CONSTRAINED_BASELINE;
|
||||
} else if (name.find("hevc") != std::string::npos) {
|
||||
@@ -305,23 +310,9 @@ bool set_quality(void *priv_data, const std::string &name, int quality) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (name.find("mediacodec") != std::string::npos) {
|
||||
if (name.find("h264") != std::string::npos) {
|
||||
if ((ret = av_opt_set(priv_data, "level", "5.1", 0)) < 0) {
|
||||
LOG_ERROR(std::string("mediacodec set opt level 5.1 failed, ret = ") +
|
||||
av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (name.find("hevc") != std::string::npos) {
|
||||
// https:en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
|
||||
if ((ret = av_opt_set(priv_data, "level", "h5.1", 0)) < 0) {
|
||||
LOG_ERROR(std::string("mediacodec set opt level h5.1 failed, ret = ") +
|
||||
av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do not force MediaCodec level here. Some Android TV vendor encoders,
|
||||
// including older Amlogic OMX implementations, reject explicit level values
|
||||
// even when they support the requested resolution and bitrate.
|
||||
// libx264 software encoder presets
|
||||
if (is_software_h264(name)) {
|
||||
const char* preset = nullptr;
|
||||
@@ -457,6 +448,13 @@ bool set_others(void *priv_data, const std::string &name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (name.find("mediacodec") != std::string::npos) {
|
||||
if ((ret = av_opt_set_int(priv_data, "ndk_codec", 1, 0)) < 0) {
|
||||
LOG_ERROR(std::string("mediacodec set ndk_codec failed, ret = ") +
|
||||
av_err2str(ret));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// NOTE: Removed idr_interval = INT_MAX for VAAPI.
|
||||
// This was disabling automatic keyframe generation.
|
||||
// The encoder should respect c->gop_size for keyframe interval.
|
||||
|
||||
@@ -137,6 +137,13 @@ public:
|
||||
av_buffer_unref(&frames_ref);
|
||||
}
|
||||
|
||||
if (name_.find("mediacodec") != std::string::npos && c_->priv_data) {
|
||||
if ((ret = av_opt_set_int(c_->priv_data, "ndk_codec", 1, 0)) < 0) {
|
||||
LOG_WARN(std::string("mediacodec decoder ndk_codec option failed, ret = ") +
|
||||
av_err2str(ret));
|
||||
}
|
||||
}
|
||||
|
||||
if ((ret = avcodec_open2(c_, codec, NULL)) < 0) {
|
||||
set_last_error(std::string("avcodec_open2 failed, ret = ") + av_err2str(ret));
|
||||
return false;
|
||||
|
||||
@@ -112,6 +112,9 @@ _exit:
|
||||
namespace {
|
||||
typedef void (*RamEncodeCallback)(const uint8_t *data, int len, int64_t pts,
|
||||
int key, const void *obj);
|
||||
typedef void (*RamEncodePacketCallback)(void *packet, const uint8_t *data,
|
||||
int len, int64_t pts, int key,
|
||||
const void *obj);
|
||||
|
||||
class FFmpegRamEncoder {
|
||||
public:
|
||||
@@ -134,6 +137,7 @@ public:
|
||||
int thread_count_ = 1;
|
||||
int gpu_ = 0;
|
||||
RamEncodeCallback callback_ = NULL;
|
||||
RamEncodePacketCallback packet_callback_ = NULL;
|
||||
int offset_[AV_NUM_DATA_POINTERS] = {0};
|
||||
bool force_keyframe_ = false; // Force next frame to be a keyframe
|
||||
|
||||
@@ -141,6 +145,7 @@ public:
|
||||
AVPixelFormat hw_pixfmt_ = AV_PIX_FMT_NONE;
|
||||
AVBufferRef *hw_device_ctx_ = NULL;
|
||||
AVFrame *hw_frame_ = NULL;
|
||||
AVFrame *borrowed_frame_ = NULL;
|
||||
|
||||
FFmpegRamEncoder(const char *name, const char *mc_name, int width, int height,
|
||||
int pixfmt, int align, int fps, int gop, int rc, int quality,
|
||||
@@ -247,6 +252,11 @@ public:
|
||||
LOG_ERROR(std::string("Could not allocate video packet"));
|
||||
return false;
|
||||
}
|
||||
borrowed_frame_ = av_frame_alloc();
|
||||
if (!borrowed_frame_) {
|
||||
LOG_ERROR(std::string("Could not allocate borrowed video frame"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* resolution must be a multiple of two */
|
||||
c_->width = width_;
|
||||
@@ -297,11 +307,19 @@ public:
|
||||
int encode(const uint8_t *data, int length, const void *obj, uint64_t ms) {
|
||||
int ret;
|
||||
|
||||
if (can_borrow_input(length)) {
|
||||
AVFrame *borrowed = wrap_borrowed_frame(data, length);
|
||||
if (!borrowed) {
|
||||
return -1;
|
||||
}
|
||||
return do_encode(borrowed, obj, ms);
|
||||
}
|
||||
|
||||
if ((ret = av_frame_make_writable(frame_)) != 0) {
|
||||
LOG_ERROR(std::string("av_frame_make_writable failed, ret = ") + av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
if ((ret = fill_frame(frame_, (uint8_t *)data, length, offset_)) != 0)
|
||||
if ((ret = fill_frame(frame_, data, length, offset_)) != 0)
|
||||
return ret;
|
||||
AVFrame *tmp_frame;
|
||||
if (hw_device_type_ != AV_HWDEVICE_TYPE_NONE) {
|
||||
@@ -317,6 +335,14 @@ public:
|
||||
return do_encode(tmp_frame, obj, ms);
|
||||
}
|
||||
|
||||
int encode_packet(const uint8_t *data, int length, const void *obj,
|
||||
uint64_t ms, RamEncodePacketCallback callback) {
|
||||
packet_callback_ = callback;
|
||||
int ret = encode(data, length, obj, ms);
|
||||
packet_callback_ = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void free_encoder() {
|
||||
if (pkt_)
|
||||
av_packet_free(&pkt_);
|
||||
@@ -324,6 +350,8 @@ public:
|
||||
av_frame_free(&frame_);
|
||||
if (hw_frame_)
|
||||
av_frame_free(&hw_frame_);
|
||||
if (borrowed_frame_)
|
||||
av_frame_free(&borrowed_frame_);
|
||||
if (hw_device_ctx_)
|
||||
av_buffer_unref(&hw_device_ctx_);
|
||||
if (c_)
|
||||
@@ -376,101 +404,203 @@ private:
|
||||
frame->pict_type = AV_PICTURE_TYPE_NONE;
|
||||
}
|
||||
|
||||
if ((ret = avcodec_send_frame(c_, frame)) < 0) {
|
||||
ret = avcodec_send_frame(c_, frame);
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
int drain_ret = receive_available_packets(obj, encoded);
|
||||
if (drain_ret < 0) {
|
||||
return drain_ret;
|
||||
}
|
||||
ret = avcodec_send_frame(c_, frame);
|
||||
}
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
return encoded ? 0 : AVERROR(EAGAIN);
|
||||
}
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(std::string("avcodec_send_frame failed, ret = ") + av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto start = util::now();
|
||||
while (ret >= 0 && util::elapsed_ms(start) < DECODE_TIMEOUT_MS) {
|
||||
if ((ret = avcodec_receive_packet(c_, pkt_)) < 0) {
|
||||
if (ret != AVERROR(EAGAIN)) {
|
||||
LOG_ERROR(std::string("avcodec_receive_packet failed, ret = ") + av_err2str(ret));
|
||||
}
|
||||
goto _exit;
|
||||
}
|
||||
if (!pkt_->data || !pkt_->size) {
|
||||
LOG_ERROR(std::string("avcodec_receive_packet failed, pkt size is 0"));
|
||||
goto _exit;
|
||||
}
|
||||
encoded = true;
|
||||
callback_(pkt_->data, pkt_->size, pkt_->pts,
|
||||
pkt_->flags & AV_PKT_FLAG_KEY, obj);
|
||||
ret = receive_available_packets(obj, encoded);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
_exit:
|
||||
av_packet_unref(pkt_);
|
||||
|
||||
// If no packet is produced for this input frame, treat it as EAGAIN.
|
||||
// This is not a fatal error: encoders may buffer internally (e.g., startup delay).
|
||||
return encoded ? 0 : AVERROR(EAGAIN);
|
||||
}
|
||||
|
||||
int fill_frame(AVFrame *frame, uint8_t *data, int data_length,
|
||||
const int *const offset) {
|
||||
int receive_available_packets(const void *obj, bool &encoded) {
|
||||
int ret = 0;
|
||||
auto start = util::now();
|
||||
|
||||
while (util::elapsed_ms(start) < DECODE_TIMEOUT_MS) {
|
||||
ret = avcodec_receive_packet(c_, pkt_);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
return 0;
|
||||
}
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(std::string("avcodec_receive_packet failed, ret = ") + av_err2str(ret));
|
||||
av_packet_unref(pkt_);
|
||||
return ret;
|
||||
}
|
||||
if (!pkt_->data || !pkt_->size) {
|
||||
LOG_WARN(std::string("avcodec_receive_packet returned empty packet"));
|
||||
av_packet_unref(pkt_);
|
||||
continue;
|
||||
}
|
||||
encoded = true;
|
||||
if (packet_callback_) {
|
||||
AVPacket *owned_pkt = av_packet_clone(pkt_);
|
||||
if (!owned_pkt) {
|
||||
LOG_ERROR("av_packet_clone failed");
|
||||
av_packet_unref(pkt_);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
packet_callback_(owned_pkt, owned_pkt->data, owned_pkt->size,
|
||||
owned_pkt->pts, owned_pkt->flags & AV_PKT_FLAG_KEY,
|
||||
obj);
|
||||
} else {
|
||||
callback_(pkt_->data, pkt_->size, pkt_->pts,
|
||||
pkt_->flags & AV_PKT_FLAG_KEY, obj);
|
||||
}
|
||||
av_packet_unref(pkt_);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int copy_plane(uint8_t *dst, int dst_stride, const uint8_t *src,
|
||||
int src_stride, int row_bytes, int rows) {
|
||||
if (!dst || !src || dst_stride < row_bytes || src_stride < row_bytes) {
|
||||
return -1;
|
||||
}
|
||||
if (rows <= 0 || row_bytes <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (dst_stride == row_bytes && src_stride == row_bytes) {
|
||||
memcpy(dst, src, static_cast<size_t>(row_bytes) * rows);
|
||||
return 0;
|
||||
}
|
||||
for (int y = 0; y < rows; y++) {
|
||||
memcpy(dst + y * dst_stride, src + y * src_stride, row_bytes);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fill_frame(AVFrame *frame, const uint8_t *data, int data_length,
|
||||
const int *const) {
|
||||
const int src_y_stride = width_;
|
||||
const int src_packed_stride = width_ * bytes_per_pixel(frame->format);
|
||||
const int src_uv_stride = width_;
|
||||
const int src_y_size = width_ * frame->height;
|
||||
const int src_420_chroma_size = (width_ / 2) * (frame->height / 2);
|
||||
switch (frame->format) {
|
||||
case AV_PIX_FMT_NV12:
|
||||
case AV_PIX_FMT_NV21:
|
||||
if (data_length <
|
||||
frame->height * (frame->linesize[0] + frame->linesize[1] / 2)) {
|
||||
frame->height * src_y_stride + frame->height / 2 * src_uv_stride) {
|
||||
LOG_ERROR(std::string("fill_frame: NV12/NV21 data length error. data_length:") +
|
||||
std::to_string(data_length) +
|
||||
", linesize[0]:" + std::to_string(frame->linesize[0]) +
|
||||
", linesize[1]:" + std::to_string(frame->linesize[1]));
|
||||
", width:" + std::to_string(width_) +
|
||||
", height:" + std::to_string(frame->height));
|
||||
return -1;
|
||||
}
|
||||
if (copy_plane(frame->data[0], frame->linesize[0], data, src_y_stride,
|
||||
width_, frame->height) != 0 ||
|
||||
copy_plane(frame->data[1], frame->linesize[1], data + src_y_size,
|
||||
src_uv_stride, width_, frame->height / 2) != 0) {
|
||||
LOG_ERROR("fill_frame: NV12/NV21 copy failed");
|
||||
return -1;
|
||||
}
|
||||
frame->data[0] = data;
|
||||
frame->data[1] = data + offset[0];
|
||||
break;
|
||||
case AV_PIX_FMT_NV16:
|
||||
case AV_PIX_FMT_NV24:
|
||||
if (data_length <
|
||||
frame->height * (frame->linesize[0] + frame->linesize[1])) {
|
||||
LOG_ERROR(std::string("fill_frame: NV16/NV24 data length error. data_length:") +
|
||||
frame->height * src_y_stride + frame->height * src_uv_stride) {
|
||||
LOG_ERROR(std::string("fill_frame: NV16 data length error. data_length:") +
|
||||
std::to_string(data_length) +
|
||||
", linesize[0]:" + std::to_string(frame->linesize[0]) +
|
||||
", linesize[1]:" + std::to_string(frame->linesize[1]));
|
||||
", width:" + std::to_string(width_) +
|
||||
", height:" + std::to_string(frame->height));
|
||||
return -1;
|
||||
}
|
||||
if (copy_plane(frame->data[0], frame->linesize[0], data, src_y_stride,
|
||||
width_, frame->height) != 0 ||
|
||||
copy_plane(frame->data[1], frame->linesize[1], data + src_y_size,
|
||||
src_uv_stride, width_, frame->height) != 0) {
|
||||
LOG_ERROR("fill_frame: NV16 copy failed");
|
||||
return -1;
|
||||
}
|
||||
frame->data[0] = data;
|
||||
frame->data[1] = data + offset[0];
|
||||
break;
|
||||
case AV_PIX_FMT_NV24: {
|
||||
const int src_nv24_uv_stride = width_ * 2;
|
||||
if (data_length <
|
||||
frame->height * src_y_stride + frame->height * src_nv24_uv_stride) {
|
||||
LOG_ERROR(std::string("fill_frame: NV24 data length error. data_length:") +
|
||||
std::to_string(data_length) +
|
||||
", width:" + std::to_string(width_) +
|
||||
", height:" + std::to_string(frame->height));
|
||||
return -1;
|
||||
}
|
||||
if (copy_plane(frame->data[0], frame->linesize[0], data, src_y_stride,
|
||||
width_, frame->height) != 0 ||
|
||||
copy_plane(frame->data[1], frame->linesize[1], data + src_y_size,
|
||||
src_nv24_uv_stride, width_ * 2, frame->height) != 0) {
|
||||
LOG_ERROR("fill_frame: NV24 copy failed");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
if (data_length <
|
||||
frame->height * (frame->linesize[0] + frame->linesize[1] / 2 +
|
||||
frame->linesize[2] / 2)) {
|
||||
width_ * frame->height + (width_ / 2) * (frame->height / 2) * 2) {
|
||||
LOG_ERROR(std::string("fill_frame: 420P data length error. data_length:") +
|
||||
std::to_string(data_length) +
|
||||
", linesize[0]:" + std::to_string(frame->linesize[0]) +
|
||||
", linesize[1]:" + std::to_string(frame->linesize[1]) +
|
||||
", linesize[2]:" + std::to_string(frame->linesize[2]));
|
||||
", width:" + std::to_string(width_) +
|
||||
", height:" + std::to_string(frame->height));
|
||||
return -1;
|
||||
}
|
||||
if (copy_plane(frame->data[0], frame->linesize[0], data, width_,
|
||||
width_, frame->height) != 0 ||
|
||||
copy_plane(frame->data[1], frame->linesize[1], data + src_y_size,
|
||||
width_ / 2, width_ / 2, frame->height / 2) != 0 ||
|
||||
copy_plane(frame->data[2], frame->linesize[2],
|
||||
data + src_y_size + src_420_chroma_size,
|
||||
width_ / 2, width_ / 2, frame->height / 2) != 0) {
|
||||
LOG_ERROR("fill_frame: 420P copy failed");
|
||||
return -1;
|
||||
}
|
||||
frame->data[0] = data;
|
||||
frame->data[1] = data + offset[0];
|
||||
frame->data[2] = data + offset[1];
|
||||
break;
|
||||
case AV_PIX_FMT_YUYV422:
|
||||
case AV_PIX_FMT_YVYU422:
|
||||
case AV_PIX_FMT_UYVY422:
|
||||
// Packed YUV 4:2:2 formats: single plane, linesize[0] = width * 2
|
||||
if (data_length < frame->height * frame->linesize[0]) {
|
||||
if (data_length < frame->height * src_packed_stride) {
|
||||
LOG_ERROR(std::string("fill_frame: YUYV422 data length error. data_length:") +
|
||||
std::to_string(data_length) +
|
||||
", linesize[0]:" + std::to_string(frame->linesize[0]) +
|
||||
", stride:" + std::to_string(src_packed_stride) +
|
||||
", height:" + std::to_string(frame->height));
|
||||
return -1;
|
||||
}
|
||||
frame->data[0] = data;
|
||||
if (copy_plane(frame->data[0], frame->linesize[0], data,
|
||||
src_packed_stride, src_packed_stride, frame->height) != 0) {
|
||||
LOG_ERROR("fill_frame: YUYV422 copy failed");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case AV_PIX_FMT_RGB24:
|
||||
case AV_PIX_FMT_BGR24:
|
||||
if (data_length < frame->height * frame->linesize[0]) {
|
||||
if (data_length < frame->height * src_packed_stride) {
|
||||
LOG_ERROR(std::string("fill_frame: RGB24/BGR24 data length error. data_length:") +
|
||||
std::to_string(data_length) +
|
||||
", linesize[0]:" + std::to_string(frame->linesize[0]) +
|
||||
", stride:" + std::to_string(src_packed_stride) +
|
||||
", height:" + std::to_string(frame->height));
|
||||
return -1;
|
||||
}
|
||||
frame->data[0] = data;
|
||||
if (copy_plane(frame->data[0], frame->linesize[0], data,
|
||||
src_packed_stride, src_packed_stride, frame->height) != 0) {
|
||||
LOG_ERROR("fill_frame: RGB24/BGR24 copy failed");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(std::string("fill_frame: unsupported format, ") +
|
||||
@@ -479,6 +609,79 @@ private:
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool can_borrow_input(int data_length) const {
|
||||
if (hw_device_type_ != AV_HWDEVICE_TYPE_NONE) {
|
||||
return false;
|
||||
}
|
||||
if (name_.find("mediacodec") == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
switch (pixfmt_) {
|
||||
case AV_PIX_FMT_NV12:
|
||||
case AV_PIX_FMT_NV21:
|
||||
return data_length >= width_ * height_ * 3 / 2;
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
return data_length >= width_ * height_ * 3 / 2;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AVFrame *wrap_borrowed_frame(const uint8_t *data, int data_length) {
|
||||
if (!borrowed_frame_) {
|
||||
return NULL;
|
||||
}
|
||||
av_frame_unref(borrowed_frame_);
|
||||
borrowed_frame_->format = pixfmt_;
|
||||
borrowed_frame_->width = width_;
|
||||
borrowed_frame_->height = height_;
|
||||
|
||||
const int y_size = width_ * height_;
|
||||
const int uv_size = y_size / 4;
|
||||
switch (pixfmt_) {
|
||||
case AV_PIX_FMT_NV12:
|
||||
case AV_PIX_FMT_NV21:
|
||||
if (data_length < y_size + y_size / 2) {
|
||||
LOG_ERROR("wrap_borrowed_frame: NV12/NV21 data length error");
|
||||
return NULL;
|
||||
}
|
||||
borrowed_frame_->data[0] = const_cast<uint8_t *>(data);
|
||||
borrowed_frame_->data[1] = const_cast<uint8_t *>(data + y_size);
|
||||
borrowed_frame_->linesize[0] = width_;
|
||||
borrowed_frame_->linesize[1] = width_;
|
||||
break;
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
if (data_length < y_size + uv_size * 2) {
|
||||
LOG_ERROR("wrap_borrowed_frame: YUV420P data length error");
|
||||
return NULL;
|
||||
}
|
||||
borrowed_frame_->data[0] = const_cast<uint8_t *>(data);
|
||||
borrowed_frame_->data[1] = const_cast<uint8_t *>(data + y_size);
|
||||
borrowed_frame_->data[2] = const_cast<uint8_t *>(data + y_size + uv_size);
|
||||
borrowed_frame_->linesize[0] = width_;
|
||||
borrowed_frame_->linesize[1] = width_ / 2;
|
||||
borrowed_frame_->linesize[2] = width_ / 2;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
return borrowed_frame_;
|
||||
}
|
||||
|
||||
int bytes_per_pixel(int pix_fmt) {
|
||||
switch (pix_fmt) {
|
||||
case AV_PIX_FMT_YUYV422:
|
||||
case AV_PIX_FMT_YVYU422:
|
||||
case AV_PIX_FMT_UYVY422:
|
||||
return 2;
|
||||
case AV_PIX_FMT_RGB24:
|
||||
case AV_PIX_FMT_BGR24:
|
||||
return 3;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -532,6 +735,25 @@ extern "C" void ffmpeg_ram_free_encoder(FFmpegRamEncoder *encoder) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int ffmpeg_ram_encode_packet(FFmpegRamEncoder *encoder,
|
||||
const uint8_t *data, int length,
|
||||
const void *obj, uint64_t ms,
|
||||
RamEncodePacketCallback callback) {
|
||||
try {
|
||||
return encoder->encode_packet(data, length, obj, ms, callback);
|
||||
} catch (const std::exception &e) {
|
||||
LOG_ERROR(std::string("encode_packet failed, ") + std::string(e.what()));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ffmpeg_ram_free_packet(void *packet) {
|
||||
AVPacket *pkt = reinterpret_cast<AVPacket *>(packet);
|
||||
if (pkt) {
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int ffmpeg_ram_set_bitrate(FFmpegRamEncoder *encoder, int kbs) {
|
||||
try {
|
||||
return encoder->set_bitrate(kbs);
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
typedef void (*RamEncodeCallback)(const uint8_t *data, int len, int64_t pts,
|
||||
int key, const void *obj);
|
||||
typedef void (*RamEncodePacketCallback)(void *packet, const uint8_t *data,
|
||||
int len, int64_t pts, int key,
|
||||
const void *obj);
|
||||
typedef void (*RamDecodeCallback)(const uint8_t *data, int len, int width,
|
||||
int height, int pixfmt, const void *obj);
|
||||
|
||||
@@ -18,7 +21,11 @@ void *ffmpeg_ram_new_encoder(const char *name, const char *mc_name, int width,
|
||||
RamEncodeCallback callback);
|
||||
int ffmpeg_ram_encode(void *encoder, const uint8_t *data, int length,
|
||||
const void *obj, int64_t ms);
|
||||
int ffmpeg_ram_encode_packet(void *encoder, const uint8_t *data, int length,
|
||||
const void *obj, int64_t ms,
|
||||
RamEncodePacketCallback callback);
|
||||
void ffmpeg_ram_free_encoder(void *encoder);
|
||||
void ffmpeg_ram_free_packet(void *packet);
|
||||
int ffmpeg_ram_get_linesize_offset_length(int pix_fmt, int width, int height,
|
||||
int align, int *linesize, int *offset,
|
||||
int *length);
|
||||
|
||||
@@ -13,7 +13,7 @@ pub enum Driver {
|
||||
FFMPEG,
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[cfg(any(windows, target_os = "linux", target_os = "android"))]
|
||||
pub(crate) fn supported_gpu(_encode: bool) -> (bool, bool, bool) {
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::ffi::c_int;
|
||||
@@ -39,6 +39,8 @@ pub(crate) fn supported_gpu(_encode: bool) -> (bool, bool, bool) {
|
||||
linux_support_amd() == 0,
|
||||
linux_support_intel() == 0,
|
||||
);
|
||||
#[cfg(target_os = "android")]
|
||||
return (false, false, false);
|
||||
#[allow(unreachable_code)]
|
||||
(false, false, false)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ use crate::{
|
||||
common::DataFormat::{self, *},
|
||||
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,
|
||||
ffmpeg_linesize_offset_length, ffmpeg_ram_encode, ffmpeg_ram_encode_packet,
|
||||
ffmpeg_ram_free_encoder, ffmpeg_ram_free_packet, ffmpeg_ram_new_encoder,
|
||||
ffmpeg_ram_request_keyframe, ffmpeg_ram_set_bitrate, CodecInfo, AV_NUM_DATA_POINTERS,
|
||||
},
|
||||
};
|
||||
#[cfg(feature = "bytes")]
|
||||
use bytes::Bytes;
|
||||
use log::trace;
|
||||
use std::{
|
||||
ffi::{c_void, CString},
|
||||
@@ -15,7 +17,7 @@ use std::{
|
||||
slice,
|
||||
};
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[cfg(any(windows, target_os = "linux", target_os = "android"))]
|
||||
use crate::common::Driver;
|
||||
|
||||
/// Timeout for encoder test in milliseconds
|
||||
@@ -26,6 +28,7 @@ const PRIORITY_AMF: i32 = 2;
|
||||
const PRIORITY_RKMPP: i32 = 3;
|
||||
const PRIORITY_VAAPI: i32 = 4;
|
||||
const PRIORITY_V4L2M2M: i32 = 5;
|
||||
const PRIORITY_MEDIACODEC: i32 = 2;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct CandidateCodecSpec {
|
||||
@@ -92,11 +95,32 @@ fn linux_support_v4l2m2m() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[cfg(any(windows, target_os = "linux", target_os = "android"))]
|
||||
fn enumerate_candidate_codecs(ctx: &EncodeContext) -> Vec<CodecInfo> {
|
||||
use log::debug;
|
||||
|
||||
let mut codecs = Vec::new();
|
||||
|
||||
if cfg!(target_os = "android") {
|
||||
push_candidate(
|
||||
&mut codecs,
|
||||
CandidateCodecSpec {
|
||||
name: "h264_mediacodec",
|
||||
format: H264,
|
||||
priority: PRIORITY_MEDIACODEC,
|
||||
},
|
||||
);
|
||||
push_candidate(
|
||||
&mut codecs,
|
||||
CandidateCodecSpec {
|
||||
name: "hevc_mediacodec",
|
||||
format: H265,
|
||||
priority: PRIORITY_MEDIACODEC,
|
||||
},
|
||||
);
|
||||
return codecs;
|
||||
}
|
||||
|
||||
let contains = |_vendor: Driver, _format: DataFormat| {
|
||||
// Without VRAM feature, we can't check SDK availability.
|
||||
// Keep the prefilter coarse and let FFmpeg validation do the real check.
|
||||
@@ -257,7 +281,13 @@ struct ProbePolicy {
|
||||
|
||||
impl ProbePolicy {
|
||||
fn for_codec(codec_name: &str) -> Self {
|
||||
if codec_name.contains("amf") {
|
||||
if codec_name.contains("mediacodec") {
|
||||
Self {
|
||||
max_attempts: 30,
|
||||
request_keyframe: true,
|
||||
accept_any_output: true,
|
||||
}
|
||||
} else if codec_name.contains("amf") {
|
||||
Self {
|
||||
max_attempts: 5,
|
||||
request_keyframe: true,
|
||||
@@ -304,11 +334,11 @@ fn log_failed_probe_attempt(
|
||||
frames: &[EncodeFrame],
|
||||
elapsed_ms: u128,
|
||||
) {
|
||||
use log::debug;
|
||||
use log::{debug, trace};
|
||||
|
||||
if policy.accept_any_output {
|
||||
if frames.is_empty() {
|
||||
debug!(
|
||||
trace!(
|
||||
"Encoder {} test produced no output on attempt {}",
|
||||
codec_name, attempt
|
||||
);
|
||||
@@ -337,7 +367,7 @@ fn log_failed_probe_attempt(
|
||||
}
|
||||
|
||||
fn validate_candidate(codec: &CodecInfo, ctx: &EncodeContext, yuv: &[u8]) -> bool {
|
||||
use log::debug;
|
||||
use log::{debug, warn};
|
||||
|
||||
debug!("Testing encoder: {}", codec.name);
|
||||
|
||||
@@ -388,13 +418,13 @@ fn validate_candidate(codec: &CodecInfo, ctx: &EncodeContext, yuv: &[u8]) -> boo
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
last_err = Some(err);
|
||||
debug!(
|
||||
"Encoder {} test attempt {} returned error: {}",
|
||||
codec.name, attempt_no, err
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
last_err = Some(err);
|
||||
warn!(
|
||||
"Encoder {} test attempt {} returned error: {}",
|
||||
codec.name, attempt_no, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,16 +437,20 @@ fn validate_candidate(codec: &CodecInfo, ctx: &EncodeContext, yuv: &[u8]) -> boo
|
||||
);
|
||||
false
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Failed to create encoder {}", codec.name);
|
||||
false
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Failed to create encoder {}", codec.name);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_software_fallback(codecs: &mut Vec<CodecInfo>) {
|
||||
use log::debug;
|
||||
|
||||
if cfg!(target_os = "android") {
|
||||
return;
|
||||
}
|
||||
|
||||
for fallback in CodecInfo::soft().into_vec() {
|
||||
if !codecs.iter().any(|codec| codec.format == fallback.format) {
|
||||
debug!(
|
||||
@@ -451,6 +485,39 @@ pub struct EncodeFrame {
|
||||
pub key: i32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "bytes")]
|
||||
pub struct EncodeBytesFrame {
|
||||
pub data: Bytes,
|
||||
pub pts: i64,
|
||||
pub key: i32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "bytes")]
|
||||
struct FfmpegPacketOwner {
|
||||
packet: *mut c_void,
|
||||
data: *const u8,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "bytes")]
|
||||
unsafe impl Send for FfmpegPacketOwner {}
|
||||
|
||||
#[cfg(feature = "bytes")]
|
||||
impl AsRef<[u8]> for FfmpegPacketOwner {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
unsafe { slice::from_raw_parts(self.data, self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bytes")]
|
||||
impl Drop for FfmpegPacketOwner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffmpeg_ram_free_packet(self.packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -543,6 +610,25 @@ impl Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bytes")]
|
||||
pub fn encode_bytes(&mut self, data: &[u8], ms: i64) -> Result<Vec<EncodeBytesFrame>, i32> {
|
||||
unsafe {
|
||||
let mut frames = Vec::<EncodeBytesFrame>::new();
|
||||
let result = ffmpeg_ram_encode_packet(
|
||||
self.codec,
|
||||
data.as_ptr(),
|
||||
data.len() as _,
|
||||
&mut frames as *mut _ as *const c_void,
|
||||
ms,
|
||||
Some(Encoder::packet_callback),
|
||||
);
|
||||
if result == -11 || result == 0 {
|
||||
return Ok(frames);
|
||||
}
|
||||
Err(result)
|
||||
}
|
||||
}
|
||||
|
||||
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>);
|
||||
@@ -554,6 +640,30 @@ impl Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bytes")]
|
||||
extern "C" fn packet_callback(
|
||||
packet: *mut c_void,
|
||||
data: *const u8,
|
||||
size: c_int,
|
||||
pts: i64,
|
||||
key: i32,
|
||||
obj: *const c_void,
|
||||
) {
|
||||
unsafe {
|
||||
let frames = &mut *(obj as *mut Vec<EncodeBytesFrame>);
|
||||
let owner = FfmpegPacketOwner {
|
||||
packet,
|
||||
data,
|
||||
len: size as usize,
|
||||
};
|
||||
frames.push(EncodeBytesFrame {
|
||||
data: Bytes::from_owner(owner),
|
||||
pts,
|
||||
key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_bitrate(&mut self, kbs: i32) -> Result<(), ()> {
|
||||
let ret = unsafe { ffmpeg_ram_set_bitrate(self.codec, kbs) };
|
||||
if ret == 0 {
|
||||
@@ -588,11 +698,11 @@ impl Encoder {
|
||||
pub fn available_encoders(ctx: EncodeContext, _sdk: Option<String>) -> Vec<CodecInfo> {
|
||||
use log::debug;
|
||||
|
||||
if !(cfg!(windows) || cfg!(target_os = "linux")) {
|
||||
if !(cfg!(windows) || cfg!(target_os = "linux") || cfg!(target_os = "android")) {
|
||||
return vec![];
|
||||
}
|
||||
let mut res = vec![];
|
||||
#[cfg(any(windows, target_os = "linux"))]
|
||||
#[cfg(any(windows, target_os = "linux", target_os = "android"))]
|
||||
let codecs = enumerate_candidate_codecs(&ctx);
|
||||
|
||||
if let Ok(yuv) = Encoder::dummy_yuv(ctx.clone()) {
|
||||
|
||||
@@ -9,12 +9,18 @@ use std::ffi::c_int;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/ffmpeg_ram_ffi.rs"));
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp"))]
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
pub mod decode;
|
||||
|
||||
// Provide a small stub on non-ARM builds so dependents can still compile, but decoder
|
||||
// construction will fail (since the C++ RKMPP decoder isn't built/linked).
|
||||
#[cfg(not(any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp")))]
|
||||
#[cfg(any(
|
||||
not(any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp")),
|
||||
target_os = "android"
|
||||
))]
|
||||
pub mod decode {
|
||||
use crate::ffmpeg::AVPixelFormat;
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
pub mod capture;
|
||||
pub mod common;
|
||||
pub mod ffmpeg;
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp"))]
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
pub mod ffmpeg_hw;
|
||||
pub mod ffmpeg_ram;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user