From 89072ad58de91009d0e802490d41a6025a76fb6a Mon Sep 17 00:00:00 2001
From: a15355447898a
Date: Fri, 23 Jan 2026 17:11:19 +0800
Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81v4l2=E7=BC=96=E7=A0=81,arm?=
=?UTF-8?q?=E6=9C=BA=E5=99=A8=E5=8E=9F=E7=94=9F=E6=9E=84=E5=BB=BA,docker?=
=?UTF-8?q?=E9=95=9C=E5=83=8F=E6=8D=A2archlinux,=E5=85=81=E8=AE=B8?=
=?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=97=B6=E7=A6=81=E7=94=A8HID?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build/Dockerfile.runtime | 31 ++++++------
build/build-images.sh | 18 ++++++-
libs/hwcodec/build.rs | 7 +--
.../cpp/common/platform/linux/linux.cpp | 15 ++++--
libs/hwcodec/cpp/common/util.cpp | 16 +++++--
.../cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp | 4 ++
libs/hwcodec/src/ffmpeg_ram/encode.rs | 47 ++++++++++---------
web/src/views/SettingsView.vue | 1 +
web/src/views/SetupView.vue | 18 +++++++
9 files changed, 107 insertions(+), 50 deletions(-)
diff --git a/build/Dockerfile.runtime b/build/Dockerfile.runtime
index 44617adf..36a0e7ef 100644
--- a/build/Dockerfile.runtime
+++ b/build/Dockerfile.runtime
@@ -1,38 +1,37 @@
# One-KVM Runtime Image
# This Dockerfile only packages pre-compiled binaries (no compilation)
# Used after cross-compiling with `cross build`
-# Using Debian 11 for maximum compatibility (GLIBC 2.31)
+# Using Arch Linux base to match rolling glibc on build hosts.
ARG TARGETPLATFORM=linux/amd64
-FROM debian:11-slim
+FROM lsiobase/arch
ARG TARGETPLATFORM
# Install runtime dependencies in a single layer
# All codec libraries (libx264, libx265, libopus) are now statically linked
# Only hardware acceleration drivers and core system libraries remain dynamic
-RUN apt-get update && \
- apt-get install -y --no-install-recommends \
- # Core runtime (all platforms) - no codec libs needed
+RUN pacman -Syu --noconfirm --needed \
ca-certificates \
- libudev1 \
- libasound2 \
- # v4l2 is handled by kernel, minimal userspace needed
- libv4l-0 \
+ systemd-libs \
+ alsa-lib \
+ libv4l \
+ libyuv \
+ ffmpeg \
&& \
# Platform-specific hardware acceleration
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
- apt-get install -y --no-install-recommends \
- libva2 libva-drm2 libva-x11-2 libx11-6 libxcb1 libxau6 libxdmcp6 libmfx1; \
+ pacman -S --noconfirm --needed \
+ libva libx11 libxcb libxau libxdmcp libmfx; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
- apt-get install -y --no-install-recommends \
- libdrm2 libva2; \
+ pacman -S --noconfirm --needed \
+ libdrm libva; \
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
- apt-get install -y --no-install-recommends \
- libdrm2 libva2; \
+ pacman -S --noconfirm --needed \
+ libdrm libva; \
fi && \
- rm -rf /var/lib/apt/lists/* && \
+ pacman -Scc --noconfirm && \
mkdir -p /etc/one-kvm/ventoy
# Copy init script
diff --git a/build/build-images.sh b/build/build-images.sh
index 106a9b6e..a5bf06bb 100755
--- a/build/build-images.sh
+++ b/build/build-images.sh
@@ -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
diff --git a/libs/hwcodec/build.rs b/libs/hwcodec/build.rs
index 791316eb..bdae4450 100644
--- a/libs/hwcodec/build.rs
+++ b/libs/hwcodec/build.rs
@@ -361,10 +361,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!(
diff --git a/libs/hwcodec/cpp/common/platform/linux/linux.cpp b/libs/hwcodec/cpp/common/platform/linux/linux.cpp
index 2d60c9ce..c9036d20 100644
--- a/libs/hwcodec/cpp/common/platform/linux/linux.cpp
+++ b/libs/hwcodec/cpp/common/platform/linux/linux.cpp
@@ -107,10 +107,19 @@ int linux_support_rkmpp() {
// Returns 0 if a M2M capable device is found, -1 otherwise
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++) {
diff --git a/libs/hwcodec/cpp/common/util.cpp b/libs/hwcodec/cpp/common/util.cpp
index a65d5a7f..0418e337 100644
--- a/libs/hwcodec/cpp/common/util.cpp
+++ b/libs/hwcodec/cpp/common/util.cpp
@@ -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
}
diff --git a/libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp b/libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp
index 52a6e2a6..b4387686 100644
--- a/libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp
+++ b/libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_decode.cpp
@@ -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
}
}
diff --git a/libs/hwcodec/src/ffmpeg_ram/encode.rs b/libs/hwcodec/src/ffmpeg_ram/encode.rs
index dff0a135..17c34442 100644
--- a/libs/hwcodec/src/ffmpeg_ram/encode.rs
+++ b/libs/hwcodec/src/ffmpeg_ram/encode.rs
@@ -353,40 +353,43 @@ impl Encoder {
let mut passed = false;
let mut last_err: Option = 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) => {
diff --git a/web/src/views/SettingsView.vue b/web/src/views/SettingsView.vue
index 98e6fa40..7fca4b7f 100644
--- a/web/src/views/SettingsView.vue
+++ b/web/src/views/SettingsView.vue
@@ -1323,6 +1323,7 @@ onMounted(async () => {
diff --git a/web/src/views/SetupView.vue b/web/src/views/SetupView.vue
index 90df3085..c177a3a7 100644
--- a/web/src/views/SetupView.vue
+++ b/web/src/views/SetupView.vue
@@ -319,6 +319,10 @@ watch(hidBackend, (newBackend) => {
if (newBackend === 'otg' && !otgUdc.value && devices.value.udc.length > 0) {
otgUdc.value = devices.value.udc[0]?.name || ''
}
+ if (newBackend === 'none') {
+ ch9329Port.value = ''
+ otgUdc.value = ''
+ }
})
onMounted(async () => {
@@ -341,6 +345,11 @@ onMounted(async () => {
otgUdc.value = result.udc[0].name
}
+ // 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
@@ -357,6 +366,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()
@@ -864,6 +877,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
CH9329 ({{ t('setup.serialHid') }})
USB OTG
+ {{ t('setup.disableHid') }}
@@ -931,6 +945,10 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
+
+
+ {{ t('setup.hidDisabledHint') }}
+