支持v4l2编码,arm机器原生构建,docker镜像换archlinux,允许初始化时禁用HID

This commit is contained in:
a15355447898a
2026-01-23 17:11:19 +08:00
parent e7d8c93bff
commit 89072ad58d
9 changed files with 107 additions and 50 deletions

View File

@@ -1,38 +1,37 @@
# One-KVM Runtime Image # One-KVM Runtime Image
# This Dockerfile only packages pre-compiled binaries (no compilation) # This Dockerfile only packages pre-compiled binaries (no compilation)
# Used after cross-compiling with `cross build` # 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 ARG TARGETPLATFORM=linux/amd64
FROM debian:11-slim FROM lsiobase/arch
ARG TARGETPLATFORM ARG TARGETPLATFORM
# Install runtime dependencies in a single layer # Install runtime dependencies in a single layer
# All codec libraries (libx264, libx265, libopus) are now statically linked # All codec libraries (libx264, libx265, libopus) are now statically linked
# Only hardware acceleration drivers and core system libraries remain dynamic # Only hardware acceleration drivers and core system libraries remain dynamic
RUN apt-get update && \ RUN pacman -Syu --noconfirm --needed \
apt-get install -y --no-install-recommends \
# Core runtime (all platforms) - no codec libs needed
ca-certificates \ ca-certificates \
libudev1 \ systemd-libs \
libasound2 \ alsa-lib \
# v4l2 is handled by kernel, minimal userspace needed libv4l \
libv4l-0 \ libyuv \
ffmpeg \
&& \ && \
# Platform-specific hardware acceleration # Platform-specific hardware acceleration
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \ if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
apt-get install -y --no-install-recommends \ pacman -S --noconfirm --needed \
libva2 libva-drm2 libva-x11-2 libx11-6 libxcb1 libxau6 libxdmcp6 libmfx1; \ libva libx11 libxcb libxau libxdmcp libmfx; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
apt-get install -y --no-install-recommends \ pacman -S --noconfirm --needed \
libdrm2 libva2; \ libdrm libva; \
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \ elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
apt-get install -y --no-install-recommends \ pacman -S --noconfirm --needed \
libdrm2 libva2; \ libdrm libva; \
fi && \ fi && \
rm -rf /var/lib/apt/lists/* && \ pacman -Scc --noconfirm && \
mkdir -p /etc/one-kvm/ventoy mkdir -p /etc/one-kvm/ventoy
# Copy init script # Copy init script

View File

@@ -19,8 +19,22 @@ ARCH_MAP=(
build_arch() { build_arch() {
local rust_target="$1" local rust_target="$1"
echo "=== Building: $rust_target (via cross with custom Dockerfile) ===" # 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" cross build --release --target "$rust_target"
fi
} }
# Main # Main

View File

@@ -361,10 +361,11 @@ mod ffmpeg {
// RKMPP decode only exists on ARM builds where FFmpeg is compiled with RKMPP support. // 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. // 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 target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
let enable_rkmpp = matches!(target_arch.as_str(), "aarch64" | "arm") let is_arm = matches!(target_arch.as_str(), "aarch64" | "arm");
|| std::env::var_os("CARGO_FEATURE_RKMPP").is_some();
if enable_rkmpp { if is_arm {
builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_decode.cpp")); builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_decode.cpp"));
} else { } else {
println!( println!(

View File

@@ -107,10 +107,19 @@ int linux_support_rkmpp() {
// Returns 0 if a M2M capable device is found, -1 otherwise // Returns 0 if a M2M capable device is found, -1 otherwise
int linux_support_v4l2m2m() { int linux_support_v4l2m2m() {
// Check common V4L2 M2M device paths used by various ARM SoCs // 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[] = { const char *m2m_devices[] = {
"/dev/video10", // Common M2M encoder device "/dev/video10",
"/dev/video11", // Common M2M decoder device "/dev/video11",
"/dev/video0", // Some SoCs use video0 for M2M "/dev/video0",
"/dev/video1",
"/dev/video2",
"/dev/video32",
}; };
for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) { for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) {

View File

@@ -10,11 +10,19 @@ extern "C" {
#include "common.h" #include "common.h"
#include "common.h"
#define LOG_MODULE "UTIL" #define LOG_MODULE "UTIL"
#include "log.h" #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 { namespace {
// Helper function: check if encoder is software H264 (libx264) // 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 // V4L2 M2M hardware encoder - minimize buffer latency
if (name.find("v4l2m2m") != std::string::npos) { if (name.find("v4l2m2m") != std::string::npos) {
// Minimize number of output buffers for lower latency // 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)); LOG_WARN(std::string("v4l2m2m set num_output_buffers failed, ret = ") + av_err2str(ret));
// Not fatal // 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)); LOG_WARN(std::string("v4l2m2m set num_capture_buffers failed, ret = ") + av_err2str(ret));
// Not fatal // Not fatal
} }

View File

@@ -55,7 +55,11 @@ public:
callback_ = callback; callback_ = callback;
if (name_.find("rkmpp") != std::string::npos) { if (name_.find("rkmpp") != std::string::npos) {
#ifdef AV_HWDEVICE_TYPE_RKMPP
hw_device_type_ = AV_HWDEVICE_TYPE_RKMPP; hw_device_type_ = AV_HWDEVICE_TYPE_RKMPP;
#else
set_last_error("RKMPP support not compiled in FFmpeg");
#endif
} }
} }

View File

@@ -353,39 +353,42 @@ impl Encoder {
let mut passed = false; let mut passed = false;
let mut last_err: Option<i32> = None; 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 { 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 pts = (attempt as i64) * 33; // 33ms is an approximation for 30 FPS (1000 / 30)
let start = std::time::Instant::now(); let start = std::time::Instant::now();
match encoder.encode(&yuv, pts) { match encoder.encode(&yuv, pts) {
Ok(frames) => { Ok(frames) => {
let elapsed = start.elapsed().as_millis(); let elapsed = start.elapsed().as_millis();
if frames.len() == 1 { if frames.len() >= 1 && elapsed < TEST_TIMEOUT_MS as _ {
if frames[0].key == 1 && elapsed < TEST_TIMEOUT_MS as _ {
debug!( debug!(
"Encoder {} test passed on attempt {}", "Encoder {} test passed on attempt {} (frames: {})",
codec.name, codec.name,
attempt + 1 attempt + 1,
frames.len()
); );
res.push(codec.clone()); res.push(codec.clone());
passed = true; passed = true;
break; break;
} else { } else if frames.is_empty() {
debug!( debug!(
"Encoder {} test failed on attempt {} - key: {}, timeout: {}ms", "Encoder {} test produced no output on attempt {}",
codec.name, codec.name,
attempt + 1, attempt + 1
frames[0].key,
elapsed
); );
}
} else { } else {
debug!( debug!(
"Encoder {} test failed on attempt {} - wrong frame count: {}", "Encoder {} test failed on attempt {} - frames: {}, timeout: {}ms",
codec.name, codec.name,
attempt + 1, attempt + 1,
frames.len() frames.len(),
elapsed
); );
} }
} }

View File

@@ -1323,6 +1323,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"> <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="ch9329">CH9329 (Serial)</option>
<option value="otg">USB OTG</option> <option value="otg">USB OTG</option>
<option value="none">{{ t('common.disabled') }}</option>
</select> </select>
</div> </div>
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2"> <div v-if="config.hid_backend === 'ch9329'" class="space-y-2">

View File

@@ -319,6 +319,10 @@ watch(hidBackend, (newBackend) => {
if (newBackend === 'otg' && !otgUdc.value && devices.value.udc.length > 0) { if (newBackend === 'otg' && !otgUdc.value && devices.value.udc.length > 0) {
otgUdc.value = devices.value.udc[0]?.name || '' otgUdc.value = devices.value.udc[0]?.name || ''
} }
if (newBackend === 'none') {
ch9329Port.value = ''
otgUdc.value = ''
}
}) })
onMounted(async () => { onMounted(async () => {
@@ -341,6 +345,11 @@ onMounted(async () => {
otgUdc.value = result.udc[0].name 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) // Auto-select audio device if available (and no video device to trigger watch)
if (result.audio.length > 0 && !audioDevice.value) { if (result.audio.length > 0 && !audioDevice.value) {
// Prefer HDMI audio device // Prefer HDMI audio device
@@ -357,6 +366,10 @@ onMounted(async () => {
// Use defaults // Use defaults
} }
if (devices.value.serial.length === 0 && devices.value.udc.length === 0) {
hidBackend.value = 'none'
}
// Load encoder backends // Load encoder backends
try { try {
const codecsResult = await streamApi.getCodecs() const codecsResult = await streamApi.getCodecs()
@@ -864,6 +877,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
CH9329 ({{ t('setup.serialHid') }}) CH9329 ({{ t('setup.serialHid') }})
</SelectItem> </SelectItem>
<SelectItem value="otg">USB OTG</SelectItem> <SelectItem value="otg">USB OTG</SelectItem>
<SelectItem value="none">{{ t('setup.disableHid') }}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -931,6 +945,10 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
</p> </p>
</div> </div>
</div> </div>
<p v-if="hidBackend === 'none'" class="text-xs text-muted-foreground">
{{ t('setup.hidDisabledHint') }}
</p>
</div> </div>
<!-- Step 4: Extensions Settings --> <!-- Step 4: Extensions Settings -->