mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
⭐ feat(runner): More runner improvements (#294)
## Description Whew.. - Steam can now run without namespaces using live-patcher (because Docker..) - Improved NVIDIA GPU selection and handling - Pipeline tests for GPU picking logic - Optimizations and cleanup all around - SSH (by default disabled) for easier instance debugging. - CachyOS' Proton because that works without namespaces (couldn't figure out how to enable automatically in Steam yet..) - Package updates and partial removal of futures (libp2p is going to switch to Tokio in next release hopefully) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - SSH server can now be enabled within the container for remote access when configured. - Added persistent live patching for Steam runtime entrypoints to improve compatibility with namespace-less applications. - Enhanced GPU selection with multi-GPU support and PCI bus ID matching for improved hardware compatibility. - Improved encoder selection by runtime testing of video encoders for better reliability. - Added WebSocket transport support in peer-to-peer networking. - Added flexible compositor and application launching with configurable commands and improved socket handling. - **Bug Fixes** - Addressed NVIDIA-specific GStreamer issues by setting new environment variables. - Improved error handling and logging for GPU and encoder selection. - Fixed process monitoring to handle patcher restarts and added cleanup logic. - Added GStreamer cache clearing workaround for Wayland socket failures. - **Improvements** - Real-time logging of container processes to standard output and error for easier monitoring. - Enhanced process management and reduced CPU usage in protocol handling loops. - Updated dependency versions for greater stability and feature support. - Improved audio capture defaults and expanded audio pipeline support. - Enhanced video pipeline setup with conditional handling for different encoder APIs and DMA-BUF support. - Refined concurrency and lifecycle management in protocol messaging for increased robustness. - Consistent namespace usage and updated crate references across the codebase. - Enhanced SSH configuration with key management, port customization, and startup verification. - Improved GPU and video encoder integration in pipeline construction. - Simplified error handling and consolidated write operations in protocol streams. - Removed Ludusavi installation from container image and updated package installations. - **Other** - Minor formatting and style changes for better code readability and maintainability. - Docker build context now ignores `.idea` directory to streamline builds. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
191c59d230
commit
41dca22d9d
@@ -1,7 +1,7 @@
|
||||
use crate::args::encoding_args::RateControl;
|
||||
use crate::gpu::{self, GPUInfo, get_gpu_by_card_path, get_gpus_by_vendor};
|
||||
use crate::gpu::{GPUInfo, get_gpu_by_card_path, get_gpus_by_vendor, get_nvidia_gpu_by_cuda_id};
|
||||
use clap::ValueEnum;
|
||||
use gst::prelude::*;
|
||||
use gstreamer::prelude::*;
|
||||
use std::error::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -107,7 +107,7 @@ impl EncoderType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct VideoEncoderInfo {
|
||||
pub name: String,
|
||||
pub codec: VideoCodec,
|
||||
@@ -146,9 +146,9 @@ impl VideoEncoderInfo {
|
||||
self.parameters.push((key.into(), value.into()));
|
||||
}
|
||||
|
||||
pub fn apply_parameters(&self, element: &gst::Element, verbose: bool) {
|
||||
pub fn apply_parameters(&self, element: &gstreamer::Element, verbose: bool) {
|
||||
for (key, value) in &self.parameters {
|
||||
if element.has_property(key) {
|
||||
if element.has_property(key, None) {
|
||||
if verbose {
|
||||
tracing::debug!("Setting property {} to {}", key, value);
|
||||
}
|
||||
@@ -191,7 +191,7 @@ where
|
||||
F: FnMut(&str) -> Option<(String, String)>,
|
||||
{
|
||||
let mut encoder_optz = encoder.clone();
|
||||
let element = match gst::ElementFactory::make(&encoder_optz.name).build() {
|
||||
let element = match gstreamer::ElementFactory::make(&encoder_optz.name).build() {
|
||||
Ok(e) => e,
|
||||
Err(_) => return encoder_optz, // Return original if element creation fails
|
||||
};
|
||||
@@ -329,16 +329,15 @@ pub fn encoder_low_latency_params(
|
||||
encoder_optz
|
||||
}
|
||||
|
||||
pub fn get_compatible_encoders() -> Vec<VideoEncoderInfo> {
|
||||
pub fn get_compatible_encoders(gpus: &Vec<GPUInfo>) -> Vec<VideoEncoderInfo> {
|
||||
let mut encoders = Vec::new();
|
||||
let registry = gst::Registry::get();
|
||||
let gpus = gpu::get_gpus();
|
||||
let registry = gstreamer::Registry::get();
|
||||
|
||||
for plugin in registry.plugins() {
|
||||
for feature in registry.features_by_plugin(plugin.plugin_name().as_str()) {
|
||||
let encoder_name = feature.name();
|
||||
|
||||
let factory = match gst::ElementFactory::find(encoder_name.as_str()) {
|
||||
let factory = match gstreamer::ElementFactory::find(encoder_name.as_str()) {
|
||||
Some(f) => f,
|
||||
None => continue,
|
||||
};
|
||||
@@ -376,9 +375,9 @@ pub fn get_compatible_encoders() -> Vec<VideoEncoderInfo> {
|
||||
match api {
|
||||
EncoderAPI::QSV | EncoderAPI::VAAPI => {
|
||||
// Safe property access with panic protection, gstreamer-rs is fun
|
||||
let path = if element.has_property("device-path") {
|
||||
let path = if element.has_property("device-path", None) {
|
||||
Some(element.property::<String>("device-path"))
|
||||
} else if element.has_property("device") {
|
||||
} else if element.has_property("device", None) {
|
||||
Some(element.property::<String>("device"))
|
||||
} else {
|
||||
None
|
||||
@@ -386,13 +385,11 @@ pub fn get_compatible_encoders() -> Vec<VideoEncoderInfo> {
|
||||
|
||||
path.and_then(|p| get_gpu_by_card_path(&gpus, &p))
|
||||
}
|
||||
EncoderAPI::NVENC if element.has_property("cuda-device-id") => {
|
||||
EncoderAPI::NVENC if element.has_property("cuda-device-id", None) => {
|
||||
let cuda_id = element.property::<u32>("cuda-device-id");
|
||||
get_gpus_by_vendor(&gpus, "nvidia")
|
||||
.get(cuda_id as usize)
|
||||
.cloned()
|
||||
get_nvidia_gpu_by_cuda_id(&gpus, cuda_id as usize)
|
||||
}
|
||||
EncoderAPI::AMF if element.has_property("device") => {
|
||||
EncoderAPI::AMF if element.has_property("device", None) => {
|
||||
let device_id = element.property::<u32>("device");
|
||||
get_gpus_by_vendor(&gpus, "amd")
|
||||
.get(device_id as usize)
|
||||
@@ -540,3 +537,140 @@ pub fn get_best_compatible_encoder(
|
||||
Err("No compatible encoder found".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the best compatible encoder that also passes test_encoder
|
||||
pub fn get_best_working_encoder(
|
||||
encoders: &Vec<VideoEncoderInfo>,
|
||||
codec: &Codec,
|
||||
encoder_type: &EncoderType,
|
||||
dma_buf: bool,
|
||||
) -> Result<VideoEncoderInfo, Box<dyn Error>> {
|
||||
let mut candidates = get_encoders_by_videocodec(
|
||||
encoders,
|
||||
match codec {
|
||||
Codec::Video(c) => c,
|
||||
Codec::Audio(_) => {
|
||||
return Err("Audio codec not supported for video encoder selection".into());
|
||||
}
|
||||
},
|
||||
);
|
||||
candidates = get_encoders_by_type(&candidates, encoder_type);
|
||||
let mut tried = Vec::new();
|
||||
while !candidates.is_empty() {
|
||||
let best = get_best_compatible_encoder(&candidates, codec, encoder_type)?;
|
||||
tracing::info!("Testing encoder: {}", best.name,);
|
||||
if test_encoder(&best, dma_buf).is_ok() {
|
||||
return Ok(best);
|
||||
} else {
|
||||
// Remove this encoder and try next best
|
||||
candidates.retain(|e| e != &best);
|
||||
tried.push(best.name.clone());
|
||||
}
|
||||
}
|
||||
Err(format!("No working encoder found (tried: {:?})", tried).into())
|
||||
}
|
||||
|
||||
/// Test if a pipeline with the given encoder can be created and set to Playing
|
||||
pub fn test_encoder(encoder: &VideoEncoderInfo, dma_buf: bool) -> Result<(), Box<dyn Error>> {
|
||||
let src = gstreamer::ElementFactory::make("waylanddisplaysrc").build()?;
|
||||
if let Some(gpu_info) = &encoder.gpu_info {
|
||||
src.set_property_from_str("render-node", gpu_info.render_path());
|
||||
}
|
||||
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let caps = gstreamer::Caps::from_str(&format!(
|
||||
"{},width=1280,height=720,framerate=30/1{}",
|
||||
if dma_buf {
|
||||
"video/x-raw(memory:DMABuf)"
|
||||
} else {
|
||||
"video/x-raw"
|
||||
},
|
||||
if dma_buf { "" } else { ",format=RGBx" }
|
||||
))?;
|
||||
caps_filter.set_property("caps", &caps);
|
||||
|
||||
let enc = gstreamer::ElementFactory::make(&encoder.name).build()?;
|
||||
let sink = gstreamer::ElementFactory::make("fakesink").build()?;
|
||||
// Apply encoder parameters
|
||||
encoder.apply_parameters(&enc, false);
|
||||
|
||||
// Create pipeline and link elements
|
||||
let pipeline = gstreamer::Pipeline::new();
|
||||
|
||||
if dma_buf && encoder.encoder_api == EncoderAPI::NVENC {
|
||||
// GL upload element
|
||||
let glupload = gstreamer::ElementFactory::make("glupload").build()?;
|
||||
// GL color convert element
|
||||
let glconvert = gstreamer::ElementFactory::make("glcolorconvert").build()?;
|
||||
// GL color convert caps
|
||||
let gl_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let gl_caps = gstreamer::Caps::from_str("video/x-raw(memory:GLMemory),format=NV12")?;
|
||||
gl_caps_filter.set_property("caps", &gl_caps);
|
||||
// CUDA upload element
|
||||
let cudaupload = gstreamer::ElementFactory::make("cudaupload").build()?;
|
||||
|
||||
pipeline.add_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&glupload,
|
||||
&glconvert,
|
||||
&gl_caps_filter,
|
||||
&cudaupload,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
gstreamer::Element::link_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&glupload,
|
||||
&glconvert,
|
||||
&gl_caps_filter,
|
||||
&cudaupload,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
} else {
|
||||
let vapostproc = gstreamer::ElementFactory::make("vapostproc").build()?;
|
||||
// VA caps filter
|
||||
let va_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let va_caps = gstreamer::Caps::from_str("video/x-raw(memory:VAMemory),format=NV12")?;
|
||||
va_caps_filter.set_property("caps", &va_caps);
|
||||
|
||||
pipeline.add_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&vapostproc,
|
||||
&va_caps_filter,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
gstreamer::Element::link_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&vapostproc,
|
||||
&va_caps_filter,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
}
|
||||
|
||||
let bus = pipeline.bus().ok_or("Pipeline has no bus")?;
|
||||
let _ = pipeline.set_state(gstreamer::State::Playing);
|
||||
for msg in bus.iter_timed(gstreamer::ClockTime::from_seconds(2)) {
|
||||
match msg.view() {
|
||||
gstreamer::MessageView::Error(err) => {
|
||||
let err_msg = format!("Pipeline error: {}", err.error());
|
||||
tracing::error!("Pipeline error, encoder test failed: {}", err_msg);
|
||||
let _ = pipeline.set_state(gstreamer::State::Null);
|
||||
return Err(err_msg.into());
|
||||
}
|
||||
gstreamer::MessageView::Eos(_) => {
|
||||
tracing::info!("Pipeline EOS received");
|
||||
let _ = pipeline.set_state(gstreamer::State::Null);
|
||||
return Err("Pipeline EOS received, encoder test failed".into());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let _ = pipeline.set_state(gstreamer::State::Null);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user