feat: Add streaming support (#125)

This adds:
- [x] Keyboard and mouse handling on the frontend
- [x] Video and audio streaming from the backend to the frontend
- [x] Input server that works with Websockets

Update - 17/11
- [ ] Master docker container to run this
- [ ] Steam runtime
- [ ] Entrypoint.sh

---------

Co-authored-by: Kristian Ollikainen <14197772+DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Kristian Ollikainen <DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
Wanjohi
2024-12-08 14:54:56 +03:00
committed by GitHub
parent 5eb21eeadb
commit 379db1c87b
137 changed files with 12737 additions and 5234 deletions

View File

@@ -0,0 +1,59 @@
pub struct AppArgs {
/// Verbose output mode
pub verbose: bool,
/// Debug the pipeline by showing a window on host
pub debug_feed: bool,
/// Debug the latency by showing time in stream
pub debug_latency: bool,
/// Virtual display resolution
pub resolution: (u32, u32),
/// Virtual display framerate
pub framerate: u32,
/// Nestri relay url
pub relay_url: String,
/// Nestri room name/identifier
pub room: String,
}
impl AppArgs {
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
Self {
verbose: matches.get_one::<String>("verbose").unwrap() == "true"
|| matches.get_one::<String>("verbose").unwrap() == "1",
debug_feed: matches.get_one::<String>("debug-feed").unwrap() == "true"
|| matches.get_one::<String>("debug-feed").unwrap() == "1",
debug_latency: matches.get_one::<String>("debug-latency").unwrap() == "true"
|| matches.get_one::<String>("debug-latency").unwrap() == "1",
resolution: {
let res = matches.get_one::<String>("resolution").unwrap().clone();
let parts: Vec<&str> = res.split('x').collect();
(
parts[0].parse::<u32>().unwrap(),
parts[1].parse::<u32>().unwrap(),
)
},
framerate: matches
.get_one::<String>("framerate")
.unwrap()
.parse::<u32>()
.unwrap(),
relay_url: matches.get_one::<String>("relay-url").unwrap().clone(),
// Generate random room name if not provided
room: matches.get_one::<String>("room")
.unwrap_or(&rand::random::<u32>().to_string())
.clone(),
}
}
pub fn debug_print(&self) {
println!("AppArgs:");
println!("> verbose: {}", self.verbose);
println!("> debug_feed: {}", self.debug_feed);
println!("> debug_latency: {}", self.debug_latency);
println!("> resolution: {}x{}", self.resolution.0, self.resolution.1);
println!("> framerate: {}", self.framerate);
println!("> relay_url: {}", self.relay_url);
println!("> room: {}", self.room);
}
}

View File

@@ -0,0 +1,41 @@
pub struct DeviceArgs {
/// GPU vendor (e.g. "intel")
pub gpu_vendor: String,
/// GPU name (e.g. "a770")
pub gpu_name: String,
/// GPU index, if multiple same GPUs are present
pub gpu_index: u32,
/// GPU card/render path, sets card explicitly from such path
pub gpu_card_path: String,
}
impl DeviceArgs {
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
Self {
gpu_vendor: matches
.get_one::<String>("gpu-vendor")
.unwrap_or(&"".to_string())
.clone(),
gpu_name: matches
.get_one::<String>("gpu-name")
.unwrap_or(&"".to_string())
.clone(),
gpu_index: matches
.get_one::<String>("gpu-index")
.unwrap()
.parse::<u32>()
.unwrap(),
gpu_card_path: matches
.get_one::<String>("gpu-card-path")
.unwrap_or(&"".to_string())
.clone(),
}
}
pub fn debug_print(&self) {
println!("DeviceArgs:");
println!("> gpu_vendor: {}", self.gpu_vendor);
println!("> gpu_name: {}", self.gpu_name);
println!("> gpu_index: {}", self.gpu_index);
println!("> gpu_card_path: {}", self.gpu_card_path);
}
}

View File

@@ -0,0 +1,190 @@
use std::ops::Deref;
#[derive(Debug, PartialEq, Eq)]
pub struct RateControlCQP {
/// Constant Quantization Parameter (CQP) quality level
pub quality: u32,
}
#[derive(Debug, PartialEq, Eq)]
pub struct RateControlVBR {
/// Target bitrate in kbps
pub target_bitrate: i32,
/// Maximum bitrate in kbps
pub max_bitrate: i32,
}
#[derive(Debug, PartialEq, Eq)]
pub struct RateControlCBR {
/// Target bitrate in kbps
pub target_bitrate: i32,
}
#[derive(Debug, PartialEq, Eq)]
pub enum RateControl {
/// Constant Quantization Parameter
CQP(RateControlCQP),
/// Variable Bitrate
VBR(RateControlVBR),
/// Constant Bitrate
CBR(RateControlCBR),
}
pub struct EncodingOptionsBase {
/// Codec (e.g. "h264", "opus" etc.)
pub codec: String,
/// Overridable encoder (e.g. "vah264lpenc", "opusenc" etc.)
pub encoder: String,
/// Rate control method (e.g. "cqp", "vbr", "cbr")
pub rate_control: RateControl,
}
impl EncodingOptionsBase {
pub fn debug_print(&self) {
println!("> Codec: {}", self.codec);
println!("> Encoder: {}", self.encoder);
match &self.rate_control {
RateControl::CQP(cqp) => {
println!("> Rate Control: CQP");
println!("-> Quality: {}", cqp.quality);
}
RateControl::VBR(vbr) => {
println!("> Rate Control: VBR");
println!("-> Target Bitrate: {}", vbr.target_bitrate);
println!("-> Max Bitrate: {}", vbr.max_bitrate);
}
RateControl::CBR(cbr) => {
println!("> Rate Control: CBR");
println!("-> Target Bitrate: {}", cbr.target_bitrate);
}
}
}
}
pub struct VideoEncodingOptions {
pub base: EncodingOptionsBase,
/// Encoder type (e.g. "hardware", "software")
pub encoder_type: String,
}
impl VideoEncodingOptions {
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
Self {
base: EncodingOptionsBase {
codec: matches.get_one::<String>("video-codec").unwrap().clone(),
encoder: matches
.get_one::<String>("video-encoder")
.unwrap_or(&"".to_string())
.clone(),
rate_control: match matches.get_one::<String>("video-rate-control").unwrap().as_str() {
"cqp" => RateControl::CQP(RateControlCQP {
quality: matches.get_one::<String>("video-cqp").unwrap().parse::<u32>().unwrap(),
}),
"cbr" => RateControl::CBR(RateControlCBR {
target_bitrate: matches.get_one::<String>("video-bitrate").unwrap().parse::<i32>().unwrap(),
}),
"vbr" => RateControl::VBR(RateControlVBR {
target_bitrate: matches.get_one::<String>("video-bitrate").unwrap().parse::<i32>().unwrap(),
max_bitrate: matches.get_one::<String>("video-bitrate-max").unwrap().parse::<i32>().unwrap(),
}),
_ => panic!("Invalid rate control method for video"),
},
},
encoder_type: matches.get_one::<String>("video-encoder-type").unwrap_or(&"hardware".to_string()).clone(),
}
}
pub fn debug_print(&self) {
println!("Video Encoding Options:");
self.base.debug_print();
println!("> Encoder Type: {}", self.encoder_type);
}
}
impl Deref for VideoEncodingOptions {
type Target = EncodingOptionsBase;
fn deref(&self) -> &Self::Target {
&self.base
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum AudioCaptureMethod {
PulseAudio,
PipeWire,
ALSA,
}
impl AudioCaptureMethod {
pub fn as_str(&self) -> &str {
match self {
AudioCaptureMethod::PulseAudio => "pulseaudio",
AudioCaptureMethod::PipeWire => "pipewire",
AudioCaptureMethod::ALSA => "alsa",
}
}
}
pub struct AudioEncodingOptions {
pub base: EncodingOptionsBase,
pub capture_method: AudioCaptureMethod,
}
impl AudioEncodingOptions {
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
Self {
base: EncodingOptionsBase {
codec: matches.get_one::<String>("audio-codec").unwrap().clone(),
encoder: matches
.get_one::<String>("audio-encoder")
.unwrap_or(&"".to_string())
.clone(),
rate_control: match matches.get_one::<String>("audio-rate-control").unwrap().as_str() {
"cbr" => RateControl::CBR(RateControlCBR {
target_bitrate: matches.get_one::<String>("audio-bitrate").unwrap().parse::<i32>().unwrap(),
}),
"vbr" => RateControl::VBR(RateControlVBR {
target_bitrate: matches.get_one::<String>("audio-bitrate").unwrap().parse::<i32>().unwrap(),
max_bitrate: matches.get_one::<String>("audio-bitrate-max").unwrap().parse::<i32>().unwrap(),
}),
_ => panic!("Invalid rate control method for audio"),
},
},
capture_method: match matches.get_one::<String>("audio-capture-method").unwrap().as_str() {
"pulseaudio" => AudioCaptureMethod::PulseAudio,
"pipewire" => AudioCaptureMethod::PipeWire,
"alsa" => AudioCaptureMethod::ALSA,
// Default to PulseAudio
_ => AudioCaptureMethod::PulseAudio,
},
}
}
pub fn debug_print(&self) {
println!("Audio Encoding Options:");
self.base.debug_print();
println!("> Capture Method: {}", self.capture_method.as_str());
}
}
impl Deref for AudioEncodingOptions {
type Target = EncodingOptionsBase;
fn deref(&self) -> &Self::Target {
&self.base
}
}
pub struct EncodingArgs {
/// Video encoder options
pub video: VideoEncodingOptions,
/// Audio encoder options
pub audio: AudioEncodingOptions,
}
impl EncodingArgs {
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
Self {
video: VideoEncodingOptions::from_matches(matches),
audio: AudioEncodingOptions::from_matches(matches),
}
}
pub fn debug_print(&self) {
println!("Encoding Arguments:");
self.video.debug_print();
self.audio.debug_print();
}
}