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') }} +