lesavka: fix sd source preview geometry
This commit is contained in:
parent
6234ee872c
commit
8273b83017
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.11.16"
|
version = "0.11.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -12,7 +12,10 @@ use gstreamer_app as gst_app;
|
|||||||
use gtk::prelude::WidgetExt;
|
use gtk::prelude::WidgetExt;
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
use gtk::{gdk, glib};
|
use gtk::{gdk, glib};
|
||||||
use lesavka_common::lesavka::{MonitorRequest, VideoPacket, relay_client::RelayClient};
|
use lesavka_common::{
|
||||||
|
eye_source::{display_size_for_source_mode, eye_source_mode_for_request},
|
||||||
|
lesavka::{MonitorRequest, VideoPacket, relay_client::RelayClient},
|
||||||
|
};
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
@ -1406,14 +1409,20 @@ fn looks_like_preview_problem(status: &str) -> bool {
|
|||||||
|
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
fn build_preview_pipeline(
|
fn build_preview_pipeline(
|
||||||
_profile: PreviewProfile,
|
profile: PreviewProfile,
|
||||||
) -> Result<(gst::Pipeline, gst_app::AppSrc, gst_app::AppSink, String)> {
|
) -> Result<(gst::Pipeline, gst_app::AppSrc, gst_app::AppSink, String)> {
|
||||||
let decoder_name = pick_h264_decoder();
|
let decoder_name = pick_h264_decoder();
|
||||||
|
let source_mode = eye_source_mode_for_request(
|
||||||
|
profile.requested_width.max(2) as u32,
|
||||||
|
profile.requested_height.max(2) as u32,
|
||||||
|
);
|
||||||
|
let (render_width, render_height) = display_size_for_source_mode(source_mode);
|
||||||
let desc = format!(
|
let desc = format!(
|
||||||
"appsrc name=src is-live=true format=time do-timestamp=true block=false ! \
|
"appsrc name=src is-live=true format=time do-timestamp=true block=false ! \
|
||||||
queue max-size-buffers=6 max-size-time=0 max-size-bytes=0 leaky=downstream ! \
|
queue max-size-buffers=6 max-size-time=0 max-size-bytes=0 leaky=downstream ! \
|
||||||
h264parse name=preview_parse disable-passthrough=true ! {decoder_name} name=decoder ! videoconvert ! \
|
h264parse name=preview_parse disable-passthrough=true ! {decoder_name} name=decoder ! videoconvert ! \
|
||||||
video/x-raw,format=RGBA,pixel-aspect-ratio=1/1 ! \
|
videoscale add-borders=false ! \
|
||||||
|
video/x-raw,format=RGBA,width=(int){render_width},height=(int){render_height},pixel-aspect-ratio=1/1 ! \
|
||||||
appsink name=sink emit-signals=false sync=false max-buffers=1 drop=true",
|
appsink name=sink emit-signals=false sync=false max-buffers=1 drop=true",
|
||||||
);
|
);
|
||||||
let pipeline = gst::parse::launch(&desc)?
|
let pipeline = gst::parse::launch(&desc)?
|
||||||
@ -1441,6 +1450,8 @@ fn build_preview_pipeline(
|
|||||||
appsink.set_caps(Some(
|
appsink.set_caps(Some(
|
||||||
&gst::Caps::builder("video/x-raw")
|
&gst::Caps::builder("video/x-raw")
|
||||||
.field("format", &"RGBA")
|
.field("format", &"RGBA")
|
||||||
|
.field("width", &(render_width as i32))
|
||||||
|
.field("height", &(render_height as i32))
|
||||||
.field("pixel-aspect-ratio", &gst::Fraction::new(1, 1))
|
.field("pixel-aspect-ratio", &gst::Fraction::new(1, 1))
|
||||||
.build(),
|
.build(),
|
||||||
));
|
));
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::devices::DeviceCatalog;
|
use super::devices::DeviceCatalog;
|
||||||
use lesavka_common::eye_source::{EyeSourceMode, default_eye_source_mode, native_eye_source_modes};
|
use lesavka_common::eye_source::{
|
||||||
|
EyeSourceMode, default_eye_source_mode, display_size_for_source_mode, native_eye_source_modes,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum InputRouting {
|
pub enum InputRouting {
|
||||||
@ -206,6 +208,15 @@ impl CaptureSizePreset {
|
|||||||
_ => Self::P1080,
|
_ => Self::P1080,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_size(self) -> (u32, u32) {
|
||||||
|
display_size_for_source_mode(self.source_mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_aspect_ratio(self) -> f32 {
|
||||||
|
let (width, height) = self.display_size();
|
||||||
|
width.max(1) as f32 / height.max(1) as f32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|||||||
@ -239,9 +239,24 @@ fn refresh_eye_feed_controls(
|
|||||||
state.breakout_size_options(monitor_id),
|
state.breakout_size_options(monitor_id),
|
||||||
state.breakout_size_preset(monitor_id),
|
state.breakout_size_preset(monitor_id),
|
||||||
);
|
);
|
||||||
|
refresh_preview_frame_ratio(widgets, monitor_id, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
|
fn refresh_preview_frame_ratio(
|
||||||
|
widgets: &super::ui_components::LauncherWidgets,
|
||||||
|
monitor_id: usize,
|
||||||
|
state: &LauncherState,
|
||||||
|
) {
|
||||||
|
let capture = state
|
||||||
|
.display_capture_size_choice(monitor_id)
|
||||||
|
.unwrap_or_else(|| state.capture_size_choice(monitor_id));
|
||||||
|
widgets.display_panes[monitor_id]
|
||||||
|
.preview_frame
|
||||||
|
.set_ratio(capture.preset.display_aspect_ratio());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
fn eye_caps_changed(state: &LauncherState, caps: &crate::handshake::PeerCaps) -> bool {
|
fn eye_caps_changed(state: &LauncherState, caps: &crate::handshake::PeerCaps) -> bool {
|
||||||
let next_width = caps.eye_width.unwrap_or(state.preview_source.width);
|
let next_width = caps.eye_width.unwrap_or(state.preview_source.width);
|
||||||
@ -499,6 +514,12 @@ fn rebind_popout_preview(
|
|||||||
) {
|
) {
|
||||||
handle.binding = binding;
|
handle.binding = binding;
|
||||||
}
|
}
|
||||||
|
let capture = state
|
||||||
|
.display_capture_size_choice(monitor_id)
|
||||||
|
.unwrap_or_else(|| state.capture_size_choice(monitor_id));
|
||||||
|
handle
|
||||||
|
.frame
|
||||||
|
.set_ratio(capture.preset.display_aspect_ratio());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
|
|||||||
@ -28,6 +28,7 @@ pub struct SummaryWidgets {
|
|||||||
pub struct DisplayPaneWidgets {
|
pub struct DisplayPaneWidgets {
|
||||||
pub root: gtk::Box,
|
pub root: gtk::Box,
|
||||||
pub stack: gtk::Stack,
|
pub stack: gtk::Stack,
|
||||||
|
pub preview_frame: gtk::AspectFrame,
|
||||||
pub picture: gtk::Picture,
|
pub picture: gtk::Picture,
|
||||||
pub stream_status: gtk::Label,
|
pub stream_status: gtk::Label,
|
||||||
pub placeholder: gtk::Label,
|
pub placeholder: gtk::Label,
|
||||||
@ -41,6 +42,7 @@ pub struct DisplayPaneWidgets {
|
|||||||
|
|
||||||
pub struct PopoutWindowHandle {
|
pub struct PopoutWindowHandle {
|
||||||
pub window: gtk::ApplicationWindow,
|
pub window: gtk::ApplicationWindow,
|
||||||
|
pub frame: gtk::AspectFrame,
|
||||||
pub picture: gtk::Picture,
|
pub picture: gtk::Picture,
|
||||||
pub status_label: gtk::Label,
|
pub status_label: gtk::Label,
|
||||||
pub binding: PreviewBinding,
|
pub binding: PreviewBinding,
|
||||||
@ -1133,6 +1135,7 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
|
|||||||
DisplayPaneWidgets {
|
DisplayPaneWidgets {
|
||||||
root,
|
root,
|
||||||
stack,
|
stack,
|
||||||
|
preview_frame,
|
||||||
picture,
|
picture,
|
||||||
stream_status,
|
stream_status,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
|||||||
@ -307,6 +307,7 @@ pub fn open_popout_window(
|
|||||||
let mut popouts = popouts.borrow_mut();
|
let mut popouts = popouts.borrow_mut();
|
||||||
popouts[monitor_id] = Some(PopoutWindowHandle {
|
popouts[monitor_id] = Some(PopoutWindowHandle {
|
||||||
window: window.clone(),
|
window: window.clone(),
|
||||||
|
frame: frame.clone(),
|
||||||
picture: picture.clone(),
|
picture: picture.clone(),
|
||||||
status_label: stream_status.clone(),
|
status_label: stream_status.clone(),
|
||||||
binding,
|
binding,
|
||||||
@ -1218,6 +1219,7 @@ mod tests {
|
|||||||
.application(&app)
|
.application(&app)
|
||||||
.title("Left")
|
.title("Left")
|
||||||
.build(),
|
.build(),
|
||||||
|
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||||
picture: gtk::Picture::new(),
|
picture: gtk::Picture::new(),
|
||||||
status_label: gtk::Label::new(None),
|
status_label: gtk::Label::new(None),
|
||||||
binding: left_binding,
|
binding: left_binding,
|
||||||
@ -1227,6 +1229,7 @@ mod tests {
|
|||||||
.application(&app)
|
.application(&app)
|
||||||
.title("Right")
|
.title("Right")
|
||||||
.build(),
|
.build(),
|
||||||
|
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||||
picture: gtk::Picture::new(),
|
picture: gtk::Picture::new(),
|
||||||
status_label: gtk::Label::new(None),
|
status_label: gtk::Label::new(None),
|
||||||
binding: right_binding,
|
binding: right_binding,
|
||||||
@ -1281,6 +1284,7 @@ mod tests {
|
|||||||
let mut slot = popouts.borrow_mut();
|
let mut slot = popouts.borrow_mut();
|
||||||
slot[0] = Some(PopoutWindowHandle {
|
slot[0] = Some(PopoutWindowHandle {
|
||||||
window,
|
window,
|
||||||
|
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||||
picture: gtk::Picture::new(),
|
picture: gtk::Picture::new(),
|
||||||
status_label: gtk::Label::new(None),
|
status_label: gtk::Label::new(None),
|
||||||
binding: PreviewBinding::test_stub(),
|
binding: PreviewBinding::test_stub(),
|
||||||
@ -1328,6 +1332,7 @@ mod tests {
|
|||||||
.application(&app)
|
.application(&app)
|
||||||
.title("Left")
|
.title("Left")
|
||||||
.build(),
|
.build(),
|
||||||
|
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||||
picture: gtk::Picture::new(),
|
picture: gtk::Picture::new(),
|
||||||
status_label: gtk::Label::new(None),
|
status_label: gtk::Label::new(None),
|
||||||
binding: PreviewBinding::test_stub(),
|
binding: PreviewBinding::test_stub(),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.11.16"
|
version = "0.11.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn banner_includes_version() {
|
fn banner_includes_version() {
|
||||||
assert_eq!(banner("0.11.16"), "lesavka-common CLI (v0.11.16)");
|
assert_eq!(banner("0.11.17"), "lesavka-common CLI (v0.11.17)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,16 @@ pub fn default_eye_source_mode() -> EyeSourceMode {
|
|||||||
GC311_H264_SOURCE_MODES[0]
|
GC311_H264_SOURCE_MODES[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_size_for_source_mode(mode: EyeSourceMode) -> (u32, u32) {
|
||||||
|
match (mode.width, mode.height) {
|
||||||
|
// GC311 exposes SD widescreen source modes with non-square pixels. Render them
|
||||||
|
// into a square-pixel frame before the GTK preview consumes them.
|
||||||
|
(720, 576) => (1024, 576),
|
||||||
|
(720, 480) => (854, 480),
|
||||||
|
_ => (mode.width, mode.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eye_source_mode_for_request(requested_width: u32, requested_height: u32) -> EyeSourceMode {
|
pub fn eye_source_mode_for_request(requested_width: u32, requested_height: u32) -> EyeSourceMode {
|
||||||
if requested_width == 0 || requested_height == 0 {
|
if requested_width == 0 || requested_height == 0 {
|
||||||
return default_eye_source_mode();
|
return default_eye_source_mode();
|
||||||
@ -96,4 +106,48 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reports_square_pixel_display_size_for_each_native_mode() {
|
||||||
|
assert_eq!(
|
||||||
|
display_size_for_source_mode(EyeSourceMode {
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
fps: 60,
|
||||||
|
}),
|
||||||
|
(1920, 1080)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
display_size_for_source_mode(EyeSourceMode {
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
fps: 60,
|
||||||
|
}),
|
||||||
|
(1280, 720)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
display_size_for_source_mode(EyeSourceMode {
|
||||||
|
width: 720,
|
||||||
|
height: 576,
|
||||||
|
fps: 50,
|
||||||
|
}),
|
||||||
|
(1024, 576)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
display_size_for_source_mode(EyeSourceMode {
|
||||||
|
width: 720,
|
||||||
|
height: 480,
|
||||||
|
fps: 60,
|
||||||
|
}),
|
||||||
|
(854, 480)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
display_size_for_source_mode(EyeSourceMode {
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
fps: 60,
|
||||||
|
}),
|
||||||
|
(640, 480)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.11.16"
|
version = "0.11.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user