mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-15 23:46:51 +08:00
fix: 补齐 ATX 控制器缺失接口并完成全项目 clippy -D warnings 修复
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
//!
|
||||
//! Provides async video capture using memory-mapped buffers.
|
||||
|
||||
use bytes::Bytes;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use bytes::Bytes;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -6,11 +6,11 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, info, warn};
|
||||
use v4l2r::nix::errno::Errno;
|
||||
use v4l2r::bindings::{v4l2_frmivalenum, v4l2_frmsizeenum};
|
||||
use v4l2r::ioctl::{
|
||||
self, Capabilities, Capability as V4l2rCapability, FormatIterator, FrmIvalTypes, FrmSizeTypes,
|
||||
};
|
||||
use v4l2r::nix::errno::Errno;
|
||||
use v4l2r::{Format as V4l2rFormat, QueueType};
|
||||
|
||||
use super::format::{PixelFormat, Resolution};
|
||||
@@ -96,7 +96,9 @@ impl VideoDevice {
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|e| AppError::VideoError(format!("Failed to open device {:?}: {}", path, e)))?;
|
||||
.map_err(|e| {
|
||||
AppError::VideoError(format!("Failed to open device {:?}: {}", path, e))
|
||||
})?;
|
||||
|
||||
Ok(Self { path, fd })
|
||||
}
|
||||
@@ -106,10 +108,9 @@ impl VideoDevice {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
debug!("Opening video device (read-only): {:?}", path);
|
||||
|
||||
let fd = File::options()
|
||||
.read(true)
|
||||
.open(&path)
|
||||
.map_err(|e| AppError::VideoError(format!("Failed to open device {:?}: {}", path, e)))?;
|
||||
let fd = File::options().read(true).open(&path).map_err(|e| {
|
||||
AppError::VideoError(format!("Failed to open device {:?}: {}", path, e))
|
||||
})?;
|
||||
|
||||
Ok(Self { path, fd })
|
||||
}
|
||||
@@ -206,8 +207,9 @@ impl VideoDevice {
|
||||
if let Some(size) = size.size() {
|
||||
match size {
|
||||
FrmSizeTypes::Discrete(d) => {
|
||||
let fps =
|
||||
self.enumerate_fps(fourcc, d.width, d.height).unwrap_or_default();
|
||||
let fps = self
|
||||
.enumerate_fps(fourcc, d.width, d.height)
|
||||
.unwrap_or_default();
|
||||
resolutions.push(ResolutionInfo::new(d.width, d.height, fps));
|
||||
}
|
||||
FrmSizeTypes::StepWise(s) => {
|
||||
@@ -225,7 +227,8 @@ impl VideoDevice {
|
||||
let fps = self
|
||||
.enumerate_fps(fourcc, res.width, res.height)
|
||||
.unwrap_or_default();
|
||||
resolutions.push(ResolutionInfo::new(res.width, res.height, fps));
|
||||
resolutions
|
||||
.push(ResolutionInfo::new(res.width, res.height, fps));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,11 +268,7 @@ impl VideoDevice {
|
||||
let mut index = 0u32;
|
||||
loop {
|
||||
match ioctl::enum_frame_intervals::<v4l2_frmivalenum>(
|
||||
&self.fd,
|
||||
index,
|
||||
fourcc,
|
||||
width,
|
||||
height,
|
||||
&self.fd, index, fourcc, width, height,
|
||||
) {
|
||||
Ok(interval) => {
|
||||
if let Some(interval) = interval.intervals() {
|
||||
@@ -411,7 +410,7 @@ impl VideoDevice {
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
priority += (max_resolution / 100000) as u32;
|
||||
priority += max_resolution / 100000;
|
||||
|
||||
// Known good drivers get bonus
|
||||
let good_drivers = ["uvcvideo", "tc358743"];
|
||||
@@ -563,15 +562,7 @@ fn sysfs_maybe_capture(path: &Path) -> bool {
|
||||
}
|
||||
|
||||
let skip_hints = [
|
||||
"codec",
|
||||
"decoder",
|
||||
"encoder",
|
||||
"isp",
|
||||
"mem2mem",
|
||||
"m2m",
|
||||
"vbi",
|
||||
"radio",
|
||||
"metadata",
|
||||
"codec", "decoder", "encoder", "isp", "mem2mem", "m2m", "vbi", "radio", "metadata",
|
||||
"output",
|
||||
];
|
||||
if skip_hints.iter().any(|hint| sysfs_name.contains(hint)) && !maybe_capture {
|
||||
|
||||
@@ -33,6 +33,7 @@ fn init_hwcodec_logging() {
|
||||
|
||||
/// H.264 encoder type (detected from hwcodec)
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum H264EncoderType {
|
||||
/// NVIDIA NVENC
|
||||
Nvenc,
|
||||
@@ -49,6 +50,7 @@ pub enum H264EncoderType {
|
||||
/// Software encoding (libx264/openh264)
|
||||
Software,
|
||||
/// No encoder available
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -67,11 +69,6 @@ impl std::fmt::Display for H264EncoderType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for H264EncoderType {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Map codec name to encoder type
|
||||
fn codec_name_to_type(name: &str) -> H264EncoderType {
|
||||
@@ -94,10 +91,12 @@ fn codec_name_to_type(name: &str) -> H264EncoderType {
|
||||
|
||||
/// Input pixel format for H264 encoder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum H264InputFormat {
|
||||
/// YUV420P (I420) - planar Y, U, V
|
||||
Yuv420p,
|
||||
/// NV12 - Y plane + interleaved UV plane (optimal for VAAPI)
|
||||
#[default]
|
||||
Nv12,
|
||||
/// NV21 - Y plane + interleaved VU plane
|
||||
Nv21,
|
||||
@@ -113,11 +112,6 @@ pub enum H264InputFormat {
|
||||
Bgr24,
|
||||
}
|
||||
|
||||
impl Default for H264InputFormat {
|
||||
fn default() -> Self {
|
||||
Self::Nv12 // Default to NV12 for VAAPI compatibility
|
||||
}
|
||||
}
|
||||
|
||||
/// H.264 encoder configuration
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -31,6 +31,7 @@ fn init_hwcodec_logging() {
|
||||
|
||||
/// H.265 encoder type (detected from hwcodec)
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum H265EncoderType {
|
||||
/// NVIDIA NVENC
|
||||
Nvenc,
|
||||
@@ -47,6 +48,7 @@ pub enum H265EncoderType {
|
||||
/// Software encoder (libx265)
|
||||
Software,
|
||||
/// No encoder available
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -65,11 +67,6 @@ impl std::fmt::Display for H265EncoderType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for H265EncoderType {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncoderBackend> for H265EncoderType {
|
||||
fn from(backend: EncoderBackend) -> Self {
|
||||
@@ -87,10 +84,12 @@ impl From<EncoderBackend> for H265EncoderType {
|
||||
|
||||
/// Input pixel format for H265 encoder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum H265InputFormat {
|
||||
/// YUV420P (I420) - planar Y, U, V
|
||||
Yuv420p,
|
||||
/// NV12 - Y plane + interleaved UV plane (optimal for hardware encoders)
|
||||
#[default]
|
||||
Nv12,
|
||||
/// NV21 - Y plane + interleaved VU plane
|
||||
Nv21,
|
||||
@@ -106,11 +105,6 @@ pub enum H265InputFormat {
|
||||
Bgr24,
|
||||
}
|
||||
|
||||
impl Default for H265InputFormat {
|
||||
fn default() -> Self {
|
||||
Self::Nv12 // Default to NV12 for hardware encoder compatibility
|
||||
}
|
||||
}
|
||||
|
||||
/// H.265 encoder configuration
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -256,8 +250,6 @@ pub fn detect_best_h265_encoder(width: u32, height: u32) -> (H265EncoderType, Op
|
||||
H265EncoderType::Rkmpp
|
||||
} else if codec.name.contains("v4l2m2m") {
|
||||
H265EncoderType::V4l2M2m
|
||||
} else if codec.name.contains("libx265") {
|
||||
H265EncoderType::Software
|
||||
} else {
|
||||
H265EncoderType::Software // Default to software for unknown
|
||||
};
|
||||
|
||||
@@ -145,6 +145,7 @@ impl EncoderBackend {
|
||||
}
|
||||
|
||||
/// Parse from string (case-insensitive)
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"vaapi" => Some(EncoderBackend::Vaapi),
|
||||
|
||||
@@ -15,12 +15,14 @@ use crate::video::format::{PixelFormat, Resolution};
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "value")]
|
||||
#[derive(Default)]
|
||||
pub enum BitratePreset {
|
||||
/// Speed priority: 1 Mbps, lowest latency, smaller GOP
|
||||
/// Best for: slow networks, remote management, low-bandwidth scenarios
|
||||
Speed,
|
||||
/// Balanced: 4 Mbps, good quality/latency tradeoff
|
||||
/// Best for: typical usage, recommended default
|
||||
#[default]
|
||||
Balanced,
|
||||
/// Quality priority: 8 Mbps, best visual quality
|
||||
/// Best for: local network, high-bandwidth scenarios, detailed work
|
||||
@@ -74,11 +76,6 @@ impl BitratePreset {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BitratePreset {
|
||||
fn default() -> Self {
|
||||
Self::Balanced
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BitratePreset {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -31,12 +31,14 @@ fn init_hwcodec_logging() {
|
||||
|
||||
/// VP8 encoder type (detected from hwcodec)
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum VP8EncoderType {
|
||||
/// VAAPI (Intel on Linux)
|
||||
Vaapi,
|
||||
/// Software encoder (libvpx)
|
||||
Software,
|
||||
/// No encoder available
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -50,11 +52,6 @@ impl std::fmt::Display for VP8EncoderType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VP8EncoderType {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncoderBackend> for VP8EncoderType {
|
||||
fn from(backend: EncoderBackend) -> Self {
|
||||
@@ -68,18 +65,15 @@ impl From<EncoderBackend> for VP8EncoderType {
|
||||
|
||||
/// Input pixel format for VP8 encoder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum VP8InputFormat {
|
||||
/// YUV420P (I420) - planar Y, U, V
|
||||
Yuv420p,
|
||||
/// NV12 - Y plane + interleaved UV plane
|
||||
#[default]
|
||||
Nv12,
|
||||
}
|
||||
|
||||
impl Default for VP8InputFormat {
|
||||
fn default() -> Self {
|
||||
Self::Nv12 // Default to NV12 for VAAPI compatibility
|
||||
}
|
||||
}
|
||||
|
||||
/// VP8 encoder configuration
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -180,8 +174,6 @@ pub fn detect_best_vp8_encoder(width: u32, height: u32) -> (VP8EncoderType, Opti
|
||||
|
||||
let encoder_type = if codec.name.contains("vaapi") {
|
||||
VP8EncoderType::Vaapi
|
||||
} else if codec.name.contains("libvpx") {
|
||||
VP8EncoderType::Software
|
||||
} else {
|
||||
VP8EncoderType::Software // Default to software for unknown
|
||||
};
|
||||
|
||||
@@ -31,12 +31,14 @@ fn init_hwcodec_logging() {
|
||||
|
||||
/// VP9 encoder type (detected from hwcodec)
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum VP9EncoderType {
|
||||
/// VAAPI (Intel on Linux)
|
||||
Vaapi,
|
||||
/// Software encoder (libvpx-vp9)
|
||||
Software,
|
||||
/// No encoder available
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -50,11 +52,6 @@ impl std::fmt::Display for VP9EncoderType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VP9EncoderType {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncoderBackend> for VP9EncoderType {
|
||||
fn from(backend: EncoderBackend) -> Self {
|
||||
@@ -68,18 +65,15 @@ impl From<EncoderBackend> for VP9EncoderType {
|
||||
|
||||
/// Input pixel format for VP9 encoder
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Default)]
|
||||
pub enum VP9InputFormat {
|
||||
/// YUV420P (I420) - planar Y, U, V
|
||||
Yuv420p,
|
||||
/// NV12 - Y plane + interleaved UV plane
|
||||
#[default]
|
||||
Nv12,
|
||||
}
|
||||
|
||||
impl Default for VP9InputFormat {
|
||||
fn default() -> Self {
|
||||
Self::Nv12 // Default to NV12 for VAAPI compatibility
|
||||
}
|
||||
}
|
||||
|
||||
/// VP9 encoder configuration
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -180,8 +174,6 @@ pub fn detect_best_vp9_encoder(width: u32, height: u32) -> (VP9EncoderType, Opti
|
||||
|
||||
let encoder_type = if codec.name.contains("vaapi") {
|
||||
VP9EncoderType::Vaapi
|
||||
} else if codec.name.contains("libvpx") {
|
||||
VP9EncoderType::Software
|
||||
} else {
|
||||
VP9EncoderType::Software // Default to software for unknown
|
||||
};
|
||||
|
||||
@@ -81,6 +81,11 @@ impl FrameBuffer {
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
/// Check if the frame buffer has no data
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FrameBuffer {
|
||||
|
||||
@@ -36,9 +36,6 @@ use crate::error::{AppError, Result};
|
||||
use crate::utils::LogThrottler;
|
||||
use crate::video::convert::{Nv12Converter, PixelConverter};
|
||||
use crate::video::decoder::MjpegTurboDecoder;
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
use hwcodec::ffmpeg_hw::{last_error_message as ffmpeg_hw_last_error, HwMjpegH26xConfig, HwMjpegH26xPipeline};
|
||||
use crate::video::v4l2r_capture::V4l2rCaptureStream;
|
||||
use crate::video::encoder::h264::{detect_best_encoder, H264Config, H264Encoder, H264InputFormat};
|
||||
use crate::video::encoder::h265::{
|
||||
detect_best_h265_encoder, H265Config, H265Encoder, H265InputFormat,
|
||||
@@ -49,6 +46,11 @@ use crate::video::encoder::vp8::{detect_best_vp8_encoder, VP8Config, VP8Encoder}
|
||||
use crate::video::encoder::vp9::{detect_best_vp9_encoder, VP9Config, VP9Encoder};
|
||||
use crate::video::format::{PixelFormat, Resolution};
|
||||
use crate::video::frame::{FrameBuffer, FrameBufferPool, VideoFrame};
|
||||
use crate::video::v4l2r_capture::V4l2rCaptureStream;
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
use hwcodec::ffmpeg_hw::{
|
||||
last_error_message as ffmpeg_hw_last_error, HwMjpegH26xConfig, HwMjpegH26xPipeline,
|
||||
};
|
||||
|
||||
/// Encoded video frame for distribution
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -508,7 +510,10 @@ impl SharedVideoPipeline {
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
if needs_mjpeg_decode
|
||||
&& is_rkmpp_encoder
|
||||
&& matches!(config.output_codec, VideoEncoderType::H264 | VideoEncoderType::H265)
|
||||
&& matches!(
|
||||
config.output_codec,
|
||||
VideoEncoderType::H264 | VideoEncoderType::H265
|
||||
)
|
||||
{
|
||||
info!(
|
||||
"Initializing FFmpeg HW MJPEG->{} pipeline (no fallback)",
|
||||
@@ -525,7 +530,11 @@ impl SharedVideoPipeline {
|
||||
thread_count: 1,
|
||||
};
|
||||
let pipeline = HwMjpegH26xPipeline::new(hw_config).map_err(|e| {
|
||||
let detail = if e.is_empty() { ffmpeg_hw_last_error() } else { e };
|
||||
let detail = if e.is_empty() {
|
||||
ffmpeg_hw_last_error()
|
||||
} else {
|
||||
e
|
||||
};
|
||||
AppError::VideoError(format!(
|
||||
"FFmpeg HW MJPEG->{} init failed: {}",
|
||||
config.output_codec, detail
|
||||
@@ -899,7 +908,11 @@ impl SharedVideoPipeline {
|
||||
|
||||
/// Get subscriber count
|
||||
pub fn subscriber_count(&self) -> usize {
|
||||
self.subscribers.read().iter().filter(|tx| !tx.is_closed()).count()
|
||||
self.subscribers
|
||||
.read()
|
||||
.iter()
|
||||
.filter(|tx| !tx.is_closed())
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Report that a receiver has lagged behind
|
||||
@@ -948,7 +961,11 @@ impl SharedVideoPipeline {
|
||||
pipeline
|
||||
.reconfigure(bitrate_kbps as i32, gop as i32)
|
||||
.map_err(|e| {
|
||||
let detail = if e.is_empty() { ffmpeg_hw_last_error() } else { e };
|
||||
let detail = if e.is_empty() {
|
||||
ffmpeg_hw_last_error()
|
||||
} else {
|
||||
e
|
||||
};
|
||||
AppError::VideoError(format!(
|
||||
"FFmpeg HW reconfigure failed: {}",
|
||||
detail
|
||||
@@ -1364,8 +1381,7 @@ impl SharedVideoPipeline {
|
||||
error!("Capture error: {}", e);
|
||||
}
|
||||
} else {
|
||||
let counter =
|
||||
suppressed_capture_errors.entry(key).or_insert(0);
|
||||
let counter = suppressed_capture_errors.entry(key).or_insert(0);
|
||||
*counter = counter.saturating_add(1);
|
||||
}
|
||||
}
|
||||
@@ -1380,7 +1396,7 @@ impl SharedVideoPipeline {
|
||||
|
||||
validate_counter = validate_counter.wrapping_add(1);
|
||||
if pixel_format.is_compressed()
|
||||
&& validate_counter % JPEG_VALIDATE_INTERVAL == 0
|
||||
&& validate_counter.is_multiple_of(JPEG_VALIDATE_INTERVAL)
|
||||
&& !VideoFrame::is_valid_jpeg_bytes(&owned[..frame_size])
|
||||
{
|
||||
continue;
|
||||
@@ -1401,7 +1417,6 @@ impl SharedVideoPipeline {
|
||||
*guard = Some(frame);
|
||||
}
|
||||
let _ = frame_seq_tx.send(sequence);
|
||||
|
||||
}
|
||||
|
||||
pipeline.running_flag.store(false, Ordering::Release);
|
||||
@@ -1466,7 +1481,11 @@ impl SharedVideoPipeline {
|
||||
}
|
||||
|
||||
let packet = pipeline.encode(raw_frame, pts_ms).map_err(|e| {
|
||||
let detail = if e.is_empty() { ffmpeg_hw_last_error() } else { e };
|
||||
let detail = if e.is_empty() {
|
||||
ffmpeg_hw_last_error()
|
||||
} else {
|
||||
e
|
||||
};
|
||||
AppError::VideoError(format!("FFmpeg HW encode failed: {}", detail))
|
||||
})?;
|
||||
|
||||
@@ -1486,9 +1505,10 @@ impl SharedVideoPipeline {
|
||||
}
|
||||
|
||||
let decoded_buf = if input_format.is_compressed() {
|
||||
let decoder = state.mjpeg_decoder.as_mut().ok_or_else(|| {
|
||||
AppError::VideoError("MJPEG decoder not initialized".to_string())
|
||||
})?;
|
||||
let decoder = state
|
||||
.mjpeg_decoder
|
||||
.as_mut()
|
||||
.ok_or_else(|| AppError::VideoError("MJPEG decoder not initialized".to_string()))?;
|
||||
let decoded = decoder.decode(raw_frame)?;
|
||||
Some(decoded)
|
||||
} else {
|
||||
@@ -1518,16 +1538,18 @@ impl SharedVideoPipeline {
|
||||
debug!("[Pipeline] Keyframe will be generated for this frame");
|
||||
}
|
||||
|
||||
let encode_result = if needs_yuv420p && state.yuv420p_converter.is_some() {
|
||||
let encode_result = if needs_yuv420p {
|
||||
// Software encoder with direct input conversion to YUV420P
|
||||
let conv = state.yuv420p_converter.as_mut().unwrap();
|
||||
let yuv420p_data = conv
|
||||
.convert(raw_frame)
|
||||
.map_err(|e| AppError::VideoError(format!("YUV420P conversion failed: {}", e)))?;
|
||||
encoder.encode_raw(yuv420p_data, pts_ms)
|
||||
} else if state.nv12_converter.is_some() {
|
||||
if let Some(conv) = state.yuv420p_converter.as_mut() {
|
||||
let yuv420p_data = conv.convert(raw_frame).map_err(|e| {
|
||||
AppError::VideoError(format!("YUV420P conversion failed: {}", e))
|
||||
})?;
|
||||
encoder.encode_raw(yuv420p_data, pts_ms)
|
||||
} else {
|
||||
encoder.encode_raw(raw_frame, pts_ms)
|
||||
}
|
||||
} else if let Some(conv) = state.nv12_converter.as_mut() {
|
||||
// Hardware encoder with input conversion to NV12
|
||||
let conv = state.nv12_converter.as_mut().unwrap();
|
||||
let nv12_data = conv
|
||||
.convert(raw_frame)
|
||||
.map_err(|e| AppError::VideoError(format!("NV12 conversion failed: {}", e)))?;
|
||||
|
||||
@@ -718,9 +718,11 @@ impl VideoStreamManager {
|
||||
/// Returns None if video capture cannot be started or pipeline creation fails.
|
||||
pub async fn subscribe_encoded_frames(
|
||||
&self,
|
||||
) -> Option<tokio::sync::mpsc::Receiver<std::sync::Arc<
|
||||
crate::video::shared_video_pipeline::EncodedVideoFrame,
|
||||
>>> {
|
||||
) -> Option<
|
||||
tokio::sync::mpsc::Receiver<
|
||||
std::sync::Arc<crate::video::shared_video_pipeline::EncodedVideoFrame>,
|
||||
>,
|
||||
> {
|
||||
// 1. Ensure video capture is initialized (for config discovery)
|
||||
if self.streamer.state().await == StreamerState::Uninitialized {
|
||||
tracing::info!("Initializing video capture for encoded frame subscription");
|
||||
@@ -756,7 +758,11 @@ impl VideoStreamManager {
|
||||
}
|
||||
|
||||
// 3. Use WebRtcStreamer to ensure the shared video pipeline is running
|
||||
match self.webrtc_streamer.ensure_video_pipeline_for_external().await {
|
||||
match self
|
||||
.webrtc_streamer
|
||||
.ensure_video_pipeline_for_external()
|
||||
.await
|
||||
{
|
||||
Ok(pipeline) => Some(pipeline.subscribe()),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to start shared video pipeline: {}", e);
|
||||
|
||||
@@ -571,11 +571,9 @@ impl Streamer {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if zero_since.is_some() {
|
||||
info!("Clients reconnected, canceling auto-pause");
|
||||
zero_since = None;
|
||||
}
|
||||
} else if zero_since.is_some() {
|
||||
info!("Clients reconnected, canceling auto-pause");
|
||||
zero_since = None;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -805,7 +803,7 @@ impl Streamer {
|
||||
|
||||
validate_counter = validate_counter.wrapping_add(1);
|
||||
if pixel_format.is_compressed()
|
||||
&& validate_counter % JPEG_VALIDATE_INTERVAL == 0
|
||||
&& validate_counter.is_multiple_of(JPEG_VALIDATE_INTERVAL)
|
||||
&& !VideoFrame::is_valid_jpeg_bytes(&owned[..frame_size])
|
||||
{
|
||||
continue;
|
||||
@@ -964,7 +962,7 @@ impl Streamer {
|
||||
*streamer.state.write().await = StreamerState::Recovering;
|
||||
|
||||
// Publish reconnecting event (every 5 attempts to avoid spam)
|
||||
if attempt == 1 || attempt % 5 == 0 {
|
||||
if attempt == 1 || attempt.is_multiple_of(5) {
|
||||
streamer
|
||||
.publish_event(SystemEvent::StreamReconnecting {
|
||||
device: device_path.clone(),
|
||||
|
||||
@@ -10,8 +10,8 @@ use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
|
||||
use tracing::{debug, warn};
|
||||
use v4l2r::bindings::{v4l2_requestbuffers, v4l2_streamparm, v4l2_streamparm__bindgen_ty_1};
|
||||
use v4l2r::ioctl::{
|
||||
self, Capabilities, Capability as V4l2rCapability, MemoryConsistency, PlaneMapping,
|
||||
QBufPlane, QBuffer, QueryBuffer, V4l2Buffer,
|
||||
self, Capabilities, Capability as V4l2rCapability, MemoryConsistency, PlaneMapping, QBufPlane,
|
||||
QBuffer, QueryBuffer, V4l2Buffer,
|
||||
};
|
||||
use v4l2r::memory::{MemoryType, MmapHandle};
|
||||
use v4l2r::{Format as V4l2rFormat, PixelFormat as V4l2rPixelFormat, QueueType};
|
||||
@@ -68,24 +68,21 @@ impl V4l2rCaptureStream {
|
||||
));
|
||||
};
|
||||
|
||||
let mut fmt: V4l2rFormat = ioctl::g_fmt(&fd, queue).map_err(|e| {
|
||||
AppError::VideoError(format!("Failed to get device format: {}", e))
|
||||
})?;
|
||||
let mut fmt: V4l2rFormat = ioctl::g_fmt(&fd, queue)
|
||||
.map_err(|e| AppError::VideoError(format!("Failed to get device format: {}", e)))?;
|
||||
|
||||
fmt.width = resolution.width;
|
||||
fmt.height = resolution.height;
|
||||
fmt.pixelformat = V4l2rPixelFormat::from(&format.to_fourcc());
|
||||
|
||||
let actual_fmt: V4l2rFormat = ioctl::s_fmt(&mut fd, (queue, &fmt)).map_err(|e| {
|
||||
AppError::VideoError(format!("Failed to set device format: {}", e))
|
||||
})?;
|
||||
let actual_fmt: V4l2rFormat = ioctl::s_fmt(&mut fd, (queue, &fmt))
|
||||
.map_err(|e| AppError::VideoError(format!("Failed to set device format: {}", e)))?;
|
||||
|
||||
let actual_resolution = Resolution::new(actual_fmt.width, actual_fmt.height);
|
||||
let actual_format = PixelFormat::from_v4l2r(actual_fmt.pixelformat).unwrap_or(format);
|
||||
|
||||
let stride = actual_fmt
|
||||
.plane_fmt
|
||||
.get(0)
|
||||
.plane_fmt.first()
|
||||
.map(|p| p.bytesperline)
|
||||
.unwrap_or_else(|| match actual_format.bytes_per_pixel() {
|
||||
Some(bpp) => actual_resolution.width * bpp as u32,
|
||||
@@ -129,10 +126,7 @@ impl V4l2rCaptureStream {
|
||||
let mut plane_maps = Vec::with_capacity(query.planes.len());
|
||||
for plane in &query.planes {
|
||||
let mapping = ioctl::mmap(&fd, plane.mem_offset, plane.length).map_err(|e| {
|
||||
AppError::VideoError(format!(
|
||||
"Failed to mmap buffer {}: {}",
|
||||
index, e
|
||||
))
|
||||
AppError::VideoError(format!("Failed to mmap buffer {}: {}", index, e))
|
||||
})?;
|
||||
plane_maps.push(mapping);
|
||||
}
|
||||
@@ -150,9 +144,8 @@ impl V4l2rCaptureStream {
|
||||
};
|
||||
|
||||
stream.queue_all_buffers()?;
|
||||
ioctl::streamon(&stream.fd, stream.queue).map_err(|e| {
|
||||
AppError::VideoError(format!("Failed to start capture stream: {}", e))
|
||||
})?;
|
||||
ioctl::streamon(&stream.fd, stream.queue)
|
||||
.map_err(|e| AppError::VideoError(format!("Failed to start capture stream: {}", e)))?;
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
@@ -172,9 +165,8 @@ impl V4l2rCaptureStream {
|
||||
pub fn next_into(&mut self, dst: &mut Vec<u8>) -> io::Result<CaptureMeta> {
|
||||
self.wait_ready()?;
|
||||
|
||||
let dqbuf: V4l2Buffer = ioctl::dqbuf(&self.fd, self.queue).map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, format!("dqbuf failed: {}", e))
|
||||
})?;
|
||||
let dqbuf: V4l2Buffer = ioctl::dqbuf(&self.fd, self.queue)
|
||||
.map_err(|e| io::Error::other(format!("dqbuf failed: {}", e)))?;
|
||||
let index = dqbuf.as_v4l2_buffer().index as usize;
|
||||
let sequence = dqbuf.as_v4l2_buffer().sequence as u64;
|
||||
|
||||
@@ -211,7 +203,7 @@ impl V4l2rCaptureStream {
|
||||
}
|
||||
|
||||
self.queue_buffer(index as u32)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
||||
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||
|
||||
Ok(CaptureMeta {
|
||||
bytes_used: total,
|
||||
@@ -240,7 +232,7 @@ impl V4l2rCaptureStream {
|
||||
}
|
||||
|
||||
fn queue_buffer(&mut self, index: u32) -> Result<()> {
|
||||
let handle = MmapHandle::default();
|
||||
let handle = MmapHandle;
|
||||
let planes = self.mappings[index as usize]
|
||||
.iter()
|
||||
.map(|mapping| {
|
||||
|
||||
@@ -326,7 +326,6 @@ impl VideoSessionManager {
|
||||
bitrate_preset: self.config.bitrate_preset,
|
||||
fps: self.config.fps,
|
||||
encoder_backend: self.config.encoder_backend,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Create new pipeline
|
||||
|
||||
Reference in New Issue
Block a user