mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-15 07:26:44 +08:00
Merge pull request #214 from a15355447898a/main
支持v4l2编码,arm机器原生构建,允许初始化时禁用HID,修改README
This commit is contained in:
@@ -212,4 +212,4 @@
|
||||
|
||||

|
||||
|
||||
林枫云主营国内外地域的精品线路业务服务器、高主频游戏服务器和大带宽服务器。
|
||||
林枫云主营国内外地域的精品线路业务服务器、高主频游戏服务器和大带宽服务器。
|
||||
|
||||
@@ -19,8 +19,22 @@ ARCH_MAP=(
|
||||
build_arch() {
|
||||
local rust_target="$1"
|
||||
|
||||
echo "=== Building: $rust_target (via cross with custom Dockerfile) ==="
|
||||
cross build --release --target "$rust_target"
|
||||
# Build frontend first
|
||||
if [ ! -d "$PROJECT_DIR/web/dist" ]; then
|
||||
echo "=== Building Frontend ==="
|
||||
cd "$PROJECT_DIR/web" && npm install && npm run build
|
||||
cd "$PROJECT_DIR"
|
||||
fi
|
||||
|
||||
local host_arch=$(rustc -vV | grep host | cut -d ' ' -f 2)
|
||||
|
||||
if [ "$rust_target" == "$host_arch" ]; then
|
||||
echo "=== Building: $rust_target (NATIVE build, skipping cross) ==="
|
||||
cargo build --release --target "$rust_target"
|
||||
else
|
||||
echo "=== Building: $rust_target (via cross) ==="
|
||||
cross build --release --target "$rust_target"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main
|
||||
|
||||
@@ -362,10 +362,11 @@ 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.
|
||||
// Also check if RKMPP is available in the current FFmpeg environment to avoid compilation errors on non-Rockchip ARM (e.g. RPi).
|
||||
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||
let enable_rkmpp = matches!(target_arch.as_str(), "aarch64" | "arm")
|
||||
|| std::env::var_os("CARGO_FEATURE_RKMPP").is_some();
|
||||
if enable_rkmpp {
|
||||
let is_arm = matches!(target_arch.as_str(), "aarch64" | "arm");
|
||||
|
||||
if is_arm {
|
||||
builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_decode.cpp"));
|
||||
} else {
|
||||
println!(
|
||||
|
||||
@@ -162,10 +162,19 @@ int linux_support_v4l2m2m() {
|
||||
};
|
||||
|
||||
// Check common V4L2 M2M device paths used by various ARM SoCs
|
||||
// /dev/video10 - Standard on many SoCs
|
||||
// /dev/video11 - Standard on many SoCs (often decoder)
|
||||
// /dev/video0 - Some platforms (like RPi) might use this
|
||||
// /dev/video1 - Alternate RPi path
|
||||
// /dev/video2 - Alternate path
|
||||
// /dev/video32 - Some Allwinner/Rockchip legacy
|
||||
const char *m2m_devices[] = {
|
||||
"/dev/video10", // Common M2M encoder device
|
||||
"/dev/video11", // Common M2M decoder device
|
||||
"/dev/video0", // Some SoCs use video0 for M2M
|
||||
"/dev/video10",
|
||||
"/dev/video11",
|
||||
"/dev/video0",
|
||||
"/dev/video1",
|
||||
"/dev/video2",
|
||||
"/dev/video32",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) {
|
||||
|
||||
@@ -10,11 +10,19 @@ extern "C" {
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#define LOG_MODULE "UTIL"
|
||||
#include "log.h"
|
||||
|
||||
#ifndef FF_PROFILE_H264_BASELINE
|
||||
#define FF_PROFILE_H264_BASELINE 66
|
||||
#endif
|
||||
#ifndef FF_PROFILE_H264_HIGH
|
||||
#define FF_PROFILE_H264_HIGH 100
|
||||
#endif
|
||||
#ifndef FF_PROFILE_HEVC_MAIN
|
||||
#define FF_PROFILE_HEVC_MAIN 1
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper function: check if encoder is software H264 (libx264)
|
||||
@@ -147,11 +155,11 @@ bool set_lantency_free(void *priv_data, const std::string &name) {
|
||||
// V4L2 M2M hardware encoder - minimize buffer latency
|
||||
if (name.find("v4l2m2m") != std::string::npos) {
|
||||
// Minimize number of output buffers for lower latency
|
||||
if ((ret = av_opt_set_int(priv_data, "num_output_buffers", 2, 0)) < 0) {
|
||||
if ((ret = av_opt_set_int(priv_data, "num_output_buffers", 4, 0)) < 0) {
|
||||
LOG_WARN(std::string("v4l2m2m set num_output_buffers failed, ret = ") + av_err2str(ret));
|
||||
// Not fatal
|
||||
}
|
||||
if ((ret = av_opt_set_int(priv_data, "num_capture_buffers", 2, 0)) < 0) {
|
||||
if ((ret = av_opt_set_int(priv_data, "num_capture_buffers", 4, 0)) < 0) {
|
||||
LOG_WARN(std::string("v4l2m2m set num_capture_buffers failed, ret = ") + av_err2str(ret));
|
||||
// Not fatal
|
||||
}
|
||||
|
||||
@@ -55,7 +55,11 @@ public:
|
||||
callback_ = callback;
|
||||
|
||||
if (name_.find("rkmpp") != std::string::npos) {
|
||||
#ifdef AV_HWDEVICE_TYPE_RKMPP
|
||||
hw_device_type_ = AV_HWDEVICE_TYPE_RKMPP;
|
||||
#else
|
||||
set_last_error("RKMPP support not compiled in FFmpeg");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -353,40 +353,43 @@ impl Encoder {
|
||||
let mut passed = false;
|
||||
let mut last_err: Option<i32> = None;
|
||||
|
||||
let max_attempts = 1;
|
||||
let max_attempts = if codec.name.contains("v4l2m2m") {
|
||||
5
|
||||
} else {
|
||||
1
|
||||
};
|
||||
for attempt in 0..max_attempts {
|
||||
encoder.request_keyframe();
|
||||
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 {
|
||||
if frames.len() >= 1 && elapsed < TEST_TIMEOUT_MS as _ {
|
||||
debug!(
|
||||
"Encoder {} test failed on attempt {} - wrong frame count: {}",
|
||||
"Encoder {} test passed on attempt {} (frames: {})",
|
||||
codec.name,
|
||||
attempt + 1,
|
||||
frames.len()
|
||||
);
|
||||
res.push(codec.clone());
|
||||
passed = true;
|
||||
break;
|
||||
} else if frames.is_empty() {
|
||||
debug!(
|
||||
"Encoder {} test produced no output on attempt {}",
|
||||
codec.name,
|
||||
attempt + 1
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Encoder {} test failed on attempt {} - frames: {}, timeout: {}ms",
|
||||
codec.name,
|
||||
attempt + 1,
|
||||
frames.len(),
|
||||
elapsed
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -1567,6 +1567,7 @@ onMounted(async () => {
|
||||
<select id="hid-backend" v-model="config.hid_backend" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
|
||||
<option value="ch9329">CH9329 (Serial)</option>
|
||||
<option value="otg">USB OTG</option>
|
||||
<option value="none">{{ t('common.disabled') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
|
||||
|
||||
@@ -372,6 +372,11 @@ onMounted(async () => {
|
||||
}
|
||||
applyOtgProfileDefault()
|
||||
|
||||
// If no HID devices exist, default to disabled to avoid blocking setup
|
||||
if (result.serial.length === 0 && result.udc.length === 0) {
|
||||
hidBackend.value = 'none'
|
||||
}
|
||||
|
||||
// Auto-select audio device if available (and no video device to trigger watch)
|
||||
if (result.audio.length > 0 && !audioDevice.value) {
|
||||
// Prefer HDMI audio device
|
||||
@@ -387,6 +392,10 @@ onMounted(async () => {
|
||||
// Use defaults
|
||||
}
|
||||
|
||||
if (devices.value.serial.length === 0 && devices.value.udc.length === 0) {
|
||||
hidBackend.value = 'none'
|
||||
}
|
||||
|
||||
// Load encoder backends
|
||||
try {
|
||||
const codecsResult = await streamApi.getCodecs()
|
||||
@@ -894,6 +903,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
CH9329 ({{ t('setup.serialHid') }})
|
||||
</SelectItem>
|
||||
<SelectItem value="otg">USB OTG</SelectItem>
|
||||
<SelectItem value="none">{{ t('setup.disableHid') }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -1001,6 +1011,10 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="hidBackend === 'none'" class="text-xs text-muted-foreground">
|
||||
{{ t('setup.hidDisabledHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Step 4: Extensions Settings -->
|
||||
|
||||
Reference in New Issue
Block a user