mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-13 02:54:26 +08:00
del: 移除安卓支持
This commit is contained in:
@@ -1,122 +0,0 @@
|
||||
//! Android FFmpeg/MediaCodec encoder glue.
|
||||
|
||||
use bytes::Bytes;
|
||||
use hwcodec::common::{Quality, RateControl};
|
||||
use hwcodec::ffmpeg::{resolve_pixel_format, AVPixelFormat};
|
||||
use hwcodec::ffmpeg_ram::encode::{EncodeContext, Encoder as HwEncoder};
|
||||
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::video::format::{PixelFormat, Resolution};
|
||||
|
||||
pub struct AndroidMediaCodecH264Encoder {
|
||||
inner: HwEncoder,
|
||||
resolution: Resolution,
|
||||
input_format: PixelFormat,
|
||||
bitrate_kbps: u32,
|
||||
}
|
||||
|
||||
impl AndroidMediaCodecH264Encoder {
|
||||
pub fn new(
|
||||
resolution: Resolution,
|
||||
input_format: PixelFormat,
|
||||
fps: u32,
|
||||
bitrate_kbps: u32,
|
||||
) -> Result<Self> {
|
||||
let pixfmt = match input_format {
|
||||
PixelFormat::Nv12 => resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
|
||||
PixelFormat::Yuv420 => {
|
||||
resolve_pixel_format("yuv420p", AVPixelFormat::AV_PIX_FMT_YUV420P)
|
||||
}
|
||||
other => {
|
||||
return Err(AppError::VideoError(format!(
|
||||
"FFmpeg h264_mediacodec accepts NV12/YUV420P memory frames; {other} requires conversion first"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: "h264_mediacodec".to_string(),
|
||||
mc_name: None,
|
||||
width: resolution.width as i32,
|
||||
height: resolution.height as i32,
|
||||
pixfmt,
|
||||
align: 1,
|
||||
fps: fps.max(1) as i32,
|
||||
gop: fps.max(1) as i32,
|
||||
rc: RateControl::RC_CBR,
|
||||
quality: Quality::Quality_Low,
|
||||
kbs: bitrate_kbps.max(1) as i32,
|
||||
q: 23,
|
||||
thread_count: 1,
|
||||
};
|
||||
|
||||
let inner = HwEncoder::new(ctx).map_err(|_| {
|
||||
AppError::VideoError("Failed to create FFmpeg h264_mediacodec encoder".to_string())
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
inner,
|
||||
resolution,
|
||||
input_format,
|
||||
bitrate_kbps: bitrate_kbps.max(1),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode_raw(&mut self, data: &[u8], pts_ms: i64) -> Result<Vec<AndroidH264Packet>> {
|
||||
let min_len = self
|
||||
.input_format
|
||||
.frame_size(self.resolution)
|
||||
.ok_or_else(|| AppError::VideoError("MediaCodec input must be raw YUV".to_string()))?;
|
||||
if data.len() < min_len {
|
||||
return Err(AppError::VideoError(format!(
|
||||
"MediaCodec {} frame too small: {} < {}",
|
||||
self.input_format,
|
||||
data.len(),
|
||||
min_len
|
||||
)));
|
||||
}
|
||||
|
||||
let packets = self
|
||||
.inner
|
||||
.encode_bytes(data, pts_ms)
|
||||
.map_err(|err| AppError::VideoError(format!("h264_mediacodec encode failed: {err}")))?;
|
||||
|
||||
Ok(packets
|
||||
.into_iter()
|
||||
.map(|packet| AndroidH264Packet {
|
||||
data: packet.data,
|
||||
pts: packet.pts,
|
||||
key_frame: packet.key == 1,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn set_bitrate(&mut self, bitrate_kbps: u32) -> Result<()> {
|
||||
self.inner
|
||||
.set_bitrate(bitrate_kbps.max(1) as i32)
|
||||
.map_err(|_| AppError::VideoError("Failed to set MediaCodec bitrate".to_string()))?;
|
||||
self.bitrate_kbps = bitrate_kbps.max(1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn request_keyframe(&mut self) {
|
||||
self.inner.request_keyframe();
|
||||
}
|
||||
|
||||
pub fn codec_name(&self) -> &str {
|
||||
"h264_mediacodec"
|
||||
}
|
||||
|
||||
pub fn input_format(&self) -> PixelFormat {
|
||||
self.input_format
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AndroidMediaCodecH264Encoder {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AndroidH264Packet {
|
||||
pub data: Bytes,
|
||||
pub pts: i64,
|
||||
pub key_frame: bool,
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
//! Android FFmpeg/MediaCodec MJPEG decoder glue.
|
||||
|
||||
use hwcodec::ffmpeg::AVPixelFormat;
|
||||
use hwcodec::ffmpeg_ram::decode::{DecodeContext, Decoder};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::video::codec::convert::Nv12Converter;
|
||||
use crate::video::format::{PixelFormat, Resolution};
|
||||
|
||||
pub struct AndroidMediaCodecMjpegDecoder {
|
||||
decoder: Decoder,
|
||||
resolution: Resolution,
|
||||
nv12_converter: Option<Nv12Converter>,
|
||||
last_output_format: Option<PixelFormat>,
|
||||
pending_frames: u32,
|
||||
}
|
||||
|
||||
impl AndroidMediaCodecMjpegDecoder {
|
||||
pub fn new(resolution: Resolution) -> Result<Self> {
|
||||
let ctx = DecodeContext {
|
||||
name: "mjpeg_mediacodec".to_string(),
|
||||
width: resolution.width as i32,
|
||||
height: resolution.height as i32,
|
||||
sw_pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
|
||||
thread_count: 1,
|
||||
};
|
||||
let decoder = Decoder::new(ctx).map_err(|_| {
|
||||
AppError::VideoError("Failed to create FFmpeg mjpeg_mediacodec decoder".to_string())
|
||||
})?;
|
||||
Ok(Self {
|
||||
decoder,
|
||||
resolution,
|
||||
nv12_converter: None,
|
||||
last_output_format: None,
|
||||
pending_frames: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decode_to_nv12(&mut self, mjpeg: &[u8]) -> Result<Vec<u8>> {
|
||||
let frames = match self.decoder.decode(mjpeg) {
|
||||
Ok(frames) => frames,
|
||||
Err(err) if err == -11 => {
|
||||
self.pending_frames += 1;
|
||||
if self.pending_frames <= 3 {
|
||||
return Err(AppError::VideoError(
|
||||
"mjpeg_mediacodec decode needs more input".to_string(),
|
||||
));
|
||||
}
|
||||
return Err(AppError::VideoError(
|
||||
"mjpeg_mediacodec decoder did not output after 3 frames".to_string(),
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(AppError::VideoError(format!(
|
||||
"mjpeg_mediacodec decode failed: {err}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
if frames.is_empty() {
|
||||
self.pending_frames += 1;
|
||||
if self.pending_frames <= 3 {
|
||||
return Err(AppError::VideoError(
|
||||
"mjpeg_mediacodec decode needs more input".to_string(),
|
||||
));
|
||||
}
|
||||
return Err(AppError::VideoError(
|
||||
"mjpeg_mediacodec decoder did not output after 3 frames".to_string(),
|
||||
));
|
||||
}
|
||||
self.pending_frames = 0;
|
||||
if frames.len() > 1 {
|
||||
warn!(
|
||||
"mjpeg_mediacodec decode returned {} frames, using last",
|
||||
frames.len()
|
||||
);
|
||||
}
|
||||
|
||||
let frame = frames.pop().ok_or_else(|| {
|
||||
AppError::VideoError("mjpeg_mediacodec decode returned empty".to_string())
|
||||
})?;
|
||||
|
||||
if frame.width as u32 != self.resolution.width
|
||||
|| frame.height as u32 != self.resolution.height
|
||||
{
|
||||
warn!(
|
||||
"mjpeg_mediacodec output size {}x{} differs from expected {}x{}",
|
||||
frame.width, frame.height, self.resolution.width, self.resolution.height
|
||||
);
|
||||
}
|
||||
|
||||
let output_format = pixel_format_from_av(frame.pixfmt).ok_or_else(|| {
|
||||
AppError::VideoError(format!(
|
||||
"mjpeg_mediacodec output pixfmt {:?} is not supported",
|
||||
frame.pixfmt
|
||||
))
|
||||
})?;
|
||||
|
||||
if self.last_output_format != Some(output_format) {
|
||||
info!("mjpeg_mediacodec output format: {}", output_format);
|
||||
self.last_output_format = Some(output_format);
|
||||
}
|
||||
|
||||
match output_format {
|
||||
PixelFormat::Nv12 => Ok(frame.data),
|
||||
PixelFormat::Nv21 => {
|
||||
let converter = self
|
||||
.nv12_converter
|
||||
.get_or_insert_with(|| Nv12Converter::nv21_to_nv12(self.resolution));
|
||||
Ok(converter.convert(&frame.data)?.to_vec())
|
||||
}
|
||||
PixelFormat::Yuv420 => {
|
||||
let converter = self
|
||||
.nv12_converter
|
||||
.get_or_insert_with(|| Nv12Converter::yuv420_to_nv12(self.resolution));
|
||||
Ok(converter.convert(&frame.data)?.to_vec())
|
||||
}
|
||||
other => Err(AppError::VideoError(format!(
|
||||
"mjpeg_mediacodec output {} cannot be converted to NV12",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pixel_format_from_av(format: AVPixelFormat) -> Option<PixelFormat> {
|
||||
match format {
|
||||
AVPixelFormat::AV_PIX_FMT_NV12 => Some(PixelFormat::Nv12),
|
||||
AVPixelFormat::AV_PIX_FMT_NV21 => Some(PixelFormat::Nv21),
|
||||
AVPixelFormat::AV_PIX_FMT_YUV420P | AVPixelFormat::AV_PIX_FMT_YUVJ420P => {
|
||||
Some(PixelFormat::Yuv420)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AndroidMediaCodecMjpegDecoder {}
|
||||
@@ -48,8 +48,6 @@ pub enum H264EncoderType {
|
||||
Rkmpp,
|
||||
/// V4L2 M2M (ARM generic) - requires hwcodec extension
|
||||
V4l2M2m,
|
||||
/// Android MediaCodec via FFmpeg
|
||||
MediaCodec,
|
||||
/// Software encoding (libx264/openh264)
|
||||
Software,
|
||||
/// No encoder available
|
||||
@@ -66,7 +64,6 @@ impl std::fmt::Display for H264EncoderType {
|
||||
H264EncoderType::Vaapi => write!(f, "VAAPI"),
|
||||
H264EncoderType::Rkmpp => write!(f, "RKMPP"),
|
||||
H264EncoderType::V4l2M2m => write!(f, "V4L2 M2M"),
|
||||
H264EncoderType::MediaCodec => write!(f, "MediaCodec"),
|
||||
H264EncoderType::Software => write!(f, "Software"),
|
||||
H264EncoderType::None => write!(f, "None"),
|
||||
}
|
||||
@@ -83,7 +80,6 @@ impl From<EncoderBackend> for H264EncoderType {
|
||||
EncoderBackend::Vaapi => H264EncoderType::Vaapi,
|
||||
EncoderBackend::Rkmpp => H264EncoderType::Rkmpp,
|
||||
EncoderBackend::V4l2m2m => H264EncoderType::V4l2M2m,
|
||||
EncoderBackend::MediaCodec => H264EncoderType::MediaCodec,
|
||||
EncoderBackend::Software => H264EncoderType::Software,
|
||||
}
|
||||
}
|
||||
@@ -196,7 +192,6 @@ pub fn get_available_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: String::new(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt: resolve_pixel_format("yuv420p", AVPixelFormat::AV_PIX_FMT_YUV420P),
|
||||
@@ -296,7 +291,6 @@ impl H264Encoder {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: codec_name.to_string(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt,
|
||||
|
||||
@@ -45,8 +45,6 @@ pub enum H265EncoderType {
|
||||
Rkmpp,
|
||||
/// V4L2 M2M (ARM generic)
|
||||
V4l2M2m,
|
||||
/// Android MediaCodec via FFmpeg
|
||||
MediaCodec,
|
||||
/// Software encoder (libx265)
|
||||
Software,
|
||||
/// No encoder available
|
||||
@@ -63,7 +61,6 @@ impl std::fmt::Display for H265EncoderType {
|
||||
H265EncoderType::Vaapi => write!(f, "VAAPI"),
|
||||
H265EncoderType::Rkmpp => write!(f, "RKMPP"),
|
||||
H265EncoderType::V4l2M2m => write!(f, "V4L2 M2M"),
|
||||
H265EncoderType::MediaCodec => write!(f, "MediaCodec"),
|
||||
H265EncoderType::Software => write!(f, "Software"),
|
||||
H265EncoderType::None => write!(f, "None"),
|
||||
}
|
||||
@@ -79,7 +76,6 @@ impl From<EncoderBackend> for H265EncoderType {
|
||||
EncoderBackend::Vaapi => H265EncoderType::Vaapi,
|
||||
EncoderBackend::Rkmpp => H265EncoderType::Rkmpp,
|
||||
EncoderBackend::V4l2m2m => H265EncoderType::V4l2M2m,
|
||||
EncoderBackend::MediaCodec => H265EncoderType::MediaCodec,
|
||||
EncoderBackend::Software => H265EncoderType::Software,
|
||||
}
|
||||
}
|
||||
@@ -199,7 +195,6 @@ pub fn get_available_h265_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: String::new(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
|
||||
@@ -373,7 +368,6 @@ impl H265Encoder {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: codec_name.to_string(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt,
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
use hwcodec::common::DataFormat;
|
||||
use hwcodec::ffmpeg_ram::CodecInfo;
|
||||
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
pub mod android_mediacodec;
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
pub mod android_mjpeg;
|
||||
pub mod convert;
|
||||
|
||||
pub mod h264;
|
||||
@@ -23,10 +19,6 @@ pub mod vp9;
|
||||
#[cfg(all(feature = "desktop", any(target_arch = "aarch64", target_arch = "arm")))]
|
||||
pub mod mjpeg_rkmpp;
|
||||
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
pub use android_mediacodec::{AndroidH264Packet, AndroidMediaCodecH264Encoder};
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
pub use android_mjpeg::AndroidMediaCodecMjpegDecoder;
|
||||
pub use convert::{MjpegToNv12Decoder, PixelConverter, Yuv420pBuffer};
|
||||
pub use h264::{H264Config, H264Encoder, H264EncoderType, H264InputFormat};
|
||||
pub use h265::{H265Config, H265Encoder, H265EncoderType, H265InputFormat};
|
||||
|
||||
@@ -96,8 +96,6 @@ pub enum EncoderBackend {
|
||||
Rkmpp,
|
||||
/// V4L2 Memory-to-Memory (ARM)
|
||||
V4l2m2m,
|
||||
/// Android MediaCodec via FFmpeg
|
||||
MediaCodec,
|
||||
/// Software encoding (libx264, libx265, libvpx)
|
||||
Software,
|
||||
}
|
||||
@@ -117,8 +115,6 @@ impl EncoderBackend {
|
||||
EncoderBackend::Rkmpp
|
||||
} else if name.contains("v4l2m2m") {
|
||||
EncoderBackend::V4l2m2m
|
||||
} else if name.contains("mediacodec") {
|
||||
EncoderBackend::MediaCodec
|
||||
} else {
|
||||
EncoderBackend::Software
|
||||
}
|
||||
@@ -138,7 +134,6 @@ impl EncoderBackend {
|
||||
EncoderBackend::Amf => "AMF",
|
||||
EncoderBackend::Rkmpp => "RKMPP",
|
||||
EncoderBackend::V4l2m2m => "V4L2 M2M",
|
||||
EncoderBackend::MediaCodec => "MediaCodec",
|
||||
EncoderBackend::Software => "Software",
|
||||
}
|
||||
}
|
||||
@@ -153,7 +148,6 @@ impl EncoderBackend {
|
||||
"amf" => Some(EncoderBackend::Amf),
|
||||
"rkmpp" => Some(EncoderBackend::Rkmpp),
|
||||
"v4l2m2m" | "v4l2" => Some(EncoderBackend::V4l2m2m),
|
||||
"mediacodec" | "android-mediacodec" => Some(EncoderBackend::MediaCodec),
|
||||
"software" | "cpu" => Some(EncoderBackend::Software),
|
||||
_ => None,
|
||||
}
|
||||
@@ -261,8 +255,8 @@ impl EncoderRegistry {
|
||||
let codec_name = match format {
|
||||
VideoEncoderType::H264 => "libx264",
|
||||
VideoEncoderType::H265 => "libx265",
|
||||
VideoEncoderType::VP8 => "libvpx",
|
||||
VideoEncoderType::VP9 => "libvpx-vp9",
|
||||
VideoEncoderType::VP8 => "libvpx_vp8",
|
||||
VideoEncoderType::VP9 => "libvpx_vp9",
|
||||
};
|
||||
|
||||
encoders.push(AvailableEncoder {
|
||||
@@ -309,10 +303,9 @@ impl EncoderRegistry {
|
||||
self.encoders.clear();
|
||||
self.detection_resolution = (width, height);
|
||||
|
||||
// Create test context for encoder detection
|
||||
// Create test context for encoder detection.
|
||||
let ctx = EncodeContext {
|
||||
name: String::new(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
|
||||
@@ -332,7 +325,6 @@ impl EncoderRegistry {
|
||||
ctx.clone(),
|
||||
Duration::from_millis(DETECT_TIMEOUT_MS),
|
||||
);
|
||||
|
||||
info!("Found {} encoders from hwcodec", all_encoders.len());
|
||||
|
||||
for codec_info in &all_encoders {
|
||||
|
||||
@@ -2,8 +2,6 @@ use serde::Serialize;
|
||||
use std::sync::mpsc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
use super::AndroidMediaCodecH264Encoder;
|
||||
use super::{
|
||||
EncoderRegistry, H264Config, H264Encoder, H265Config, H265Encoder, VP8Config, VP8Encoder,
|
||||
VP9Config, VP9Encoder, VideoEncoderType,
|
||||
@@ -237,32 +235,6 @@ fn run_smoke_test(
|
||||
}
|
||||
|
||||
fn run_h264_smoke_test(resolution: Resolution, codec_name_ffmpeg: &str) -> Result<()> {
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
if codec_name_ffmpeg == "h264_mediacodec" {
|
||||
let mut encoder = AndroidMediaCodecH264Encoder::new(
|
||||
resolution,
|
||||
PixelFormat::Nv12,
|
||||
30,
|
||||
bitrate_kbps_for_resolution(resolution),
|
||||
)?;
|
||||
encoder.request_keyframe();
|
||||
let frame = build_nv12_test_frame(
|
||||
resolution,
|
||||
PixelFormat::Nv12.frame_size(resolution).unwrap_or(0),
|
||||
);
|
||||
|
||||
for sequence in 0..SELF_CHECK_FRAME_ATTEMPTS {
|
||||
let frames = encoder.encode_raw(&frame, pts_ms(sequence))?;
|
||||
if frames.iter().any(|frame| !frame.data.is_empty()) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
return Err(AppError::VideoError(
|
||||
"Encoder produced no output after multiple frames".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut encoder = H264Encoder::with_codec(
|
||||
H264Config::low_latency(resolution, bitrate_kbps_for_resolution(resolution)),
|
||||
codec_name_ffmpeg,
|
||||
|
||||
@@ -130,7 +130,6 @@ pub fn get_available_vp8_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: String::new(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
|
||||
@@ -271,7 +270,6 @@ impl VP8Encoder {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: codec_name.to_string(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt,
|
||||
|
||||
@@ -130,7 +130,6 @@ pub fn get_available_vp9_encoders(width: u32, height: u32) -> Vec<CodecInfo> {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: String::new(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt: resolve_pixel_format("nv12", AVPixelFormat::AV_PIX_FMT_NV12),
|
||||
@@ -271,7 +270,6 @@ impl VP9Encoder {
|
||||
|
||||
let ctx = EncodeContext {
|
||||
name: codec_name.to_string(),
|
||||
mc_name: None,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
pixfmt,
|
||||
|
||||
@@ -963,19 +963,6 @@ pub fn enumerate_devices() -> Result<Vec<VideoDeviceInfo>> {
|
||||
// for a single MIPI CSI pipeline. Keep only the highest-priority node per
|
||||
// (driver, bus_info) group so users see one device instead of ~11.
|
||||
dedup_platform_subdevices(&mut devices);
|
||||
devices.retain(|device| {
|
||||
let hide = should_hide_android_platform_node(device);
|
||||
if hide {
|
||||
debug!(
|
||||
"Hiding Android platform video node: {} ({}) {}",
|
||||
device.name,
|
||||
device.driver,
|
||||
device.path.display()
|
||||
);
|
||||
}
|
||||
!hide
|
||||
});
|
||||
|
||||
info!("Found {} video capture devices", devices.len());
|
||||
Ok(devices)
|
||||
}
|
||||
@@ -1055,33 +1042,6 @@ fn dedup_platform_subdevices(devices: &mut Vec<VideoDeviceInfo>) {
|
||||
});
|
||||
}
|
||||
|
||||
fn should_hide_android_platform_node(device: &VideoDeviceInfo) -> bool {
|
||||
if !cfg!(feature = "android") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let driver = device.driver.to_ascii_lowercase();
|
||||
let name = device.name.to_ascii_lowercase();
|
||||
let card = device.card.to_ascii_lowercase();
|
||||
let usb_device = driver == "uvcvideo" || device.bus_info.starts_with("usb-");
|
||||
let known_bridge =
|
||||
driver.contains("rkcif") || driver.contains("rk_hdmirx") || driver.contains("tc358743");
|
||||
if usb_device || known_bridge {
|
||||
return false;
|
||||
}
|
||||
|
||||
matches!(
|
||||
driver.as_str(),
|
||||
"ionvideo" | "amlvideo" | "amlvideo2" | "videosync"
|
||||
) || matches!(
|
||||
name.as_str(),
|
||||
"ionvideo" | "amlvideo" | "amlvideo2" | "videosync"
|
||||
) || matches!(
|
||||
card.as_str(),
|
||||
"ionvideo" | "amlvideo" | "amlvideo2" | "videosync"
|
||||
)
|
||||
}
|
||||
|
||||
/// rkcif registers many `/dev/video*` queues; probing all in parallel can
|
||||
/// contend and time out. Keep one node per board (lowest `videoN`).
|
||||
fn collapse_rkcif_probe_candidates(candidates: &mut Vec<PathBuf>) {
|
||||
@@ -1185,20 +1145,6 @@ fn sysfs_maybe_capture(path: &Path) -> bool {
|
||||
.to_lowercase();
|
||||
let driver = extract_uevent_value(&uevent, "driver");
|
||||
|
||||
if cfg!(feature = "android") {
|
||||
let platform_skip = ["ionvideo", "amlvideo", "amlvideo2", "videosync"];
|
||||
let driver_skip = driver
|
||||
.as_ref()
|
||||
.is_some_and(|driver| platform_skip.iter().any(|hint| driver == hint));
|
||||
if driver_skip || platform_skip.iter().any(|hint| sysfs_name == *hint) {
|
||||
debug!(
|
||||
"Skipping Android platform video node {:?}: {}",
|
||||
path, sysfs_name
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let mut maybe_capture = false;
|
||||
let capture_hints = [
|
||||
"capture",
|
||||
|
||||
@@ -8,21 +8,19 @@ pub mod codec_constraints;
|
||||
pub mod device;
|
||||
pub mod format;
|
||||
pub mod frame;
|
||||
#[cfg(any(feature = "android", feature = "desktop"))]
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mod pipeline;
|
||||
pub mod signal;
|
||||
#[cfg(any(feature = "android", feature = "desktop"))]
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mod stream_manager;
|
||||
#[cfg(any(feature = "android", feature = "desktop"))]
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mod streamer;
|
||||
#[cfg(any(feature = "android", feature = "desktop"))]
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mod traits;
|
||||
#[cfg(any(feature = "android", feature = "desktop"))]
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mod types;
|
||||
|
||||
pub use capture::{CaptureMeta, CaptureStream};
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
pub use codec::{AndroidH264Packet, AndroidMediaCodecH264Encoder};
|
||||
pub use codec::{H264Encoder, H264EncoderType, JpegEncoder, PixelConverter, Yuv420pBuffer};
|
||||
pub use device::{VideoDevice, VideoDeviceInfo};
|
||||
pub use format::PixelFormat;
|
||||
|
||||
@@ -6,16 +6,9 @@ use crate::video::codec::registry::{EncoderBackend, EncoderRegistry, VideoEncode
|
||||
use crate::video::codec::traits::EncoderConfig;
|
||||
use crate::video::codec::vp8::{VP8Config, VP8Encoder};
|
||||
use crate::video::codec::vp9::{VP9Config, VP9Encoder};
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
use crate::video::codec::AndroidMediaCodecH264Encoder;
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
use crate::video::codec::AndroidMediaCodecMjpegDecoder;
|
||||
use crate::video::format::{PixelFormat, Resolution};
|
||||
use bytes::Bytes;
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
use hwcodec::ffmpeg_hw::{
|
||||
last_error_message as ffmpeg_hw_last_error, HwMjpegH26xConfig, HwMjpegH26xPipeline,
|
||||
};
|
||||
@@ -29,15 +22,9 @@ pub(super) struct EncoderThreadState {
|
||||
pub(super) nv12_converter: Option<Nv12Converter>,
|
||||
pub(super) yuv420p_converter: Option<PixelConverter>,
|
||||
pub(super) encoder_needs_yuv420p: bool,
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
pub(super) ffmpeg_hw_pipeline: Option<HwMjpegH26xPipeline>,
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
pub(super) ffmpeg_hw_enabled: bool,
|
||||
pub(super) fps: u32,
|
||||
pub(super) codec: VideoEncoderType,
|
||||
@@ -129,35 +116,6 @@ impl VideoEncoderTrait for H265EncoderWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
struct AndroidMediaCodecH264EncoderWrapper(AndroidMediaCodecH264Encoder);
|
||||
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
impl VideoEncoderTrait for AndroidMediaCodecH264EncoderWrapper {
|
||||
fn encode_raw(&mut self, data: &[u8], pts_ms: i64) -> Result<Vec<EncodedFrame>> {
|
||||
let frames = self.0.encode_raw(data, pts_ms)?;
|
||||
Ok(frames
|
||||
.into_iter()
|
||||
.map(|f| EncodedFrame {
|
||||
data: f.data,
|
||||
key: if f.key_frame { 1 } else { 0 },
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn set_bitrate(&mut self, bitrate_kbps: u32) -> Result<()> {
|
||||
self.0.set_bitrate(bitrate_kbps)
|
||||
}
|
||||
|
||||
fn codec_name(&self) -> &str {
|
||||
self.0.codec_name()
|
||||
}
|
||||
|
||||
fn request_keyframe(&mut self) {
|
||||
self.0.request_keyframe()
|
||||
}
|
||||
}
|
||||
|
||||
struct VP8EncoderWrapper(VP8Encoder);
|
||||
|
||||
impl VideoEncoderTrait for VP8EncoderWrapper {
|
||||
@@ -209,50 +167,12 @@ impl VideoEncoderTrait for VP9EncoderWrapper {
|
||||
}
|
||||
|
||||
pub(super) enum MjpegDecoderKind {
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
AndroidMediaCodec {
|
||||
decoder: AndroidMediaCodecMjpegDecoder,
|
||||
fallback: Box<MjpegDecoderKind>,
|
||||
fallback_active: bool,
|
||||
output: Vec<u8>,
|
||||
},
|
||||
Libyuv {
|
||||
decoder: MjpegToNv12Decoder,
|
||||
},
|
||||
Libyuv { decoder: MjpegToNv12Decoder },
|
||||
}
|
||||
|
||||
impl MjpegDecoderKind {
|
||||
pub(super) fn decode(&mut self, data: &[u8]) -> Result<&[u8]> {
|
||||
match self {
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
MjpegDecoderKind::AndroidMediaCodec {
|
||||
decoder,
|
||||
fallback,
|
||||
fallback_active,
|
||||
output,
|
||||
} => {
|
||||
if !*fallback_active {
|
||||
match decoder.decode_to_nv12(data) {
|
||||
Ok(decoded) => {
|
||||
*output = decoded;
|
||||
return Ok(output.as_slice());
|
||||
}
|
||||
Err(AppError::VideoError(message))
|
||||
if message.contains("needs more input") =>
|
||||
{
|
||||
return Err(AppError::VideoError(message));
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Android MediaCodec MJPEG decode failed; falling back to libyuv MJPEG->NV12: {}",
|
||||
err
|
||||
);
|
||||
*fallback_active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback.decode(data)
|
||||
}
|
||||
MjpegDecoderKind::Libyuv { decoder } => decoder.decode(data),
|
||||
}
|
||||
}
|
||||
@@ -265,40 +185,6 @@ fn libyuv_mjpeg_decoder(resolution: Resolution) -> MjpegDecoderKind {
|
||||
}
|
||||
|
||||
fn create_mjpeg_decoder(resolution: Resolution) -> Result<(MjpegDecoderKind, PixelFormat)> {
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
{
|
||||
if std::env::var_os("ONE_KVM_ANDROID_MJPEG_MEDIACODEC").is_none() {
|
||||
info!("MJPEG input detected, using libyuv decoder (MJPEG -> NV12)");
|
||||
return Ok((libyuv_mjpeg_decoder(resolution), PixelFormat::Nv12));
|
||||
}
|
||||
|
||||
info!("MJPEG input detected, trying Android MediaCodec decoder (MJPEG -> NV12)");
|
||||
match AndroidMediaCodecMjpegDecoder::new(resolution) {
|
||||
Ok(decoder) => {
|
||||
info!("Using Android MediaCodec MJPEG decoder");
|
||||
return Ok((
|
||||
MjpegDecoderKind::AndroidMediaCodec {
|
||||
decoder,
|
||||
fallback: Box::new(libyuv_mjpeg_decoder(resolution)),
|
||||
fallback_active: false,
|
||||
output: Vec::with_capacity(
|
||||
PixelFormat::Nv12
|
||||
.frame_size(resolution)
|
||||
.unwrap_or((resolution.width * resolution.height * 3 / 2) as usize),
|
||||
),
|
||||
},
|
||||
PixelFormat::Nv12,
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Android MediaCodec MJPEG decoder unavailable; using libyuv MJPEG->NV12: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("MJPEG input detected, using libyuv decoder (MJPEG -> NV12)");
|
||||
Ok((libyuv_mjpeg_decoder(resolution), PixelFormat::Nv12))
|
||||
}
|
||||
@@ -400,15 +286,9 @@ pub(super) fn build_encoder_state(
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
let is_rkmpp_encoder = selected_codec_name.contains("rkmpp");
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
if needs_mjpeg_decode
|
||||
&& is_rkmpp_encoder
|
||||
&& matches!(
|
||||
@@ -448,15 +328,9 @@ pub(super) fn build_encoder_state(
|
||||
nv12_converter: None,
|
||||
yuv420p_converter: None,
|
||||
encoder_needs_yuv420p: false,
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
ffmpeg_hw_pipeline: Some(pipeline),
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
ffmpeg_hw_enabled: true,
|
||||
fps: config.fps,
|
||||
codec: config.output_codec,
|
||||
@@ -495,40 +369,7 @@ pub(super) fn build_encoder_state(
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "android-mediacodec")]
|
||||
{
|
||||
if codec_name == "h264_mediacodec" {
|
||||
info!(
|
||||
"Creating Android MediaCodec H264 encoder for {:?} input",
|
||||
input_format
|
||||
);
|
||||
let pixel_format = match input_format {
|
||||
H264InputFormat::Nv12 => PixelFormat::Nv12,
|
||||
H264InputFormat::Yuv420p => PixelFormat::Yuv420,
|
||||
other => {
|
||||
return Err(AppError::VideoError(format!(
|
||||
"Android MediaCodec H264 does not support {:?} direct input",
|
||||
other
|
||||
)));
|
||||
}
|
||||
};
|
||||
let encoder = AndroidMediaCodecH264Encoder::new(
|
||||
config.resolution,
|
||||
pixel_format,
|
||||
config.fps,
|
||||
config.bitrate_kbps(),
|
||||
)?;
|
||||
info!("Created Android MediaCodec H264 encoder");
|
||||
Box::new(AndroidMediaCodecH264EncoderWrapper(encoder))
|
||||
} else {
|
||||
create_h264_encoder(config, input_format, &codec_name)?
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "android-mediacodec"))]
|
||||
{
|
||||
create_h264_encoder(config, input_format, &codec_name)?
|
||||
}
|
||||
create_h264_encoder(config, input_format, &codec_name)?
|
||||
}
|
||||
VideoEncoderType::H265 => {
|
||||
let codec_name = selected_codec_name.clone();
|
||||
@@ -622,11 +463,6 @@ pub(super) fn build_encoder_state(
|
||||
pipeline_input_format,
|
||||
PixelFormat::Nv12 | PixelFormat::Nv16 | PixelFormat::Nv21 | PixelFormat::Yuv420
|
||||
)
|
||||
} else if codec_name.contains("mediacodec") {
|
||||
matches!(
|
||||
pipeline_input_format,
|
||||
PixelFormat::Nv12 | PixelFormat::Yuv420
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -676,15 +512,9 @@ pub(super) fn build_encoder_state(
|
||||
nv12_converter,
|
||||
yuv420p_converter,
|
||||
encoder_needs_yuv420p: needs_yuv420p,
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
ffmpeg_hw_pipeline: None,
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
ffmpeg_hw_enabled: false,
|
||||
fps: config.fps,
|
||||
codec: config.output_codec,
|
||||
@@ -708,12 +538,6 @@ fn h264_direct_input_format(
|
||||
PixelFormat::Nv24 => Some(H264InputFormat::Nv24),
|
||||
_ => None,
|
||||
}
|
||||
} else if codec_name.contains("mediacodec") {
|
||||
match input_format {
|
||||
PixelFormat::Nv12 => Some(H264InputFormat::Nv12),
|
||||
PixelFormat::Yuv420 => Some(H264InputFormat::Yuv420p),
|
||||
_ => None,
|
||||
}
|
||||
} else if codec_name.contains("libx264") {
|
||||
match input_format {
|
||||
PixelFormat::Nv12 => Some(H264InputFormat::Nv12),
|
||||
|
||||
@@ -61,10 +61,7 @@ use crate::video::signal::SignalStatus;
|
||||
|
||||
const MIN_CAPTURE_FRAME_SIZE: usize = 128;
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
use hwcodec::ffmpeg_hw::last_error_message as ffmpeg_hw_last_error;
|
||||
|
||||
/// Encoded video frame for distribution
|
||||
@@ -484,15 +481,9 @@ impl SharedVideoPipeline {
|
||||
fn apply_cmd(&self, state: &mut EncoderThreadState, cmd: PipelineCmd) -> Result<()> {
|
||||
match cmd {
|
||||
PipelineCmd::SetBitrate { bitrate_kbps, gop } => {
|
||||
#[cfg(any(
|
||||
not(any(target_arch = "aarch64", target_arch = "arm")),
|
||||
target_os = "android"
|
||||
))]
|
||||
#[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
|
||||
let _ = gop;
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
if state.ffmpeg_hw_enabled {
|
||||
if let Some(ref mut pipeline) = state.ffmpeg_hw_pipeline {
|
||||
pipeline
|
||||
@@ -659,7 +650,7 @@ impl SharedVideoPipeline {
|
||||
*guard = Some(cmd_tx);
|
||||
}
|
||||
|
||||
// Encoder loop uses a dedicated OS thread because FFmpeg/MediaCodec work is synchronous.
|
||||
// Encoder loop uses a dedicated OS thread because FFmpeg work is synchronous.
|
||||
{
|
||||
let pipeline = pipeline.clone();
|
||||
let latest_frame = latest_frame.clone();
|
||||
@@ -1289,10 +1280,7 @@ impl SharedVideoPipeline {
|
||||
current_ts_us.saturating_sub(start_ts_us) / 1000
|
||||
};
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "aarch64", target_arch = "arm"),
|
||||
not(target_os = "android")
|
||||
))]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
if state.ffmpeg_hw_enabled {
|
||||
if input_format != PixelFormat::Mjpeg {
|
||||
return Err(AppError::VideoError(
|
||||
|
||||
Reference in New Issue
Block a user