lesavka: fix sd source preview geometry
This commit is contained in:
parent
6234ee872c
commit
8273b83017
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.11.16"
|
||||
version = "0.11.17"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -12,7 +12,10 @@ use gstreamer_app as gst_app;
|
||||
use gtk::prelude::WidgetExt;
|
||||
#[cfg(not(coverage))]
|
||||
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))]
|
||||
use std::collections::VecDeque;
|
||||
#[cfg(not(coverage))]
|
||||
@ -1406,14 +1409,20 @@ fn looks_like_preview_problem(status: &str) -> bool {
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
fn build_preview_pipeline(
|
||||
_profile: PreviewProfile,
|
||||
profile: PreviewProfile,
|
||||
) -> Result<(gst::Pipeline, gst_app::AppSrc, gst_app::AppSink, String)> {
|
||||
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!(
|
||||
"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 ! \
|
||||
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",
|
||||
);
|
||||
let pipeline = gst::parse::launch(&desc)?
|
||||
@ -1441,6 +1450,8 @@ fn build_preview_pipeline(
|
||||
appsink.set_caps(Some(
|
||||
&gst::Caps::builder("video/x-raw")
|
||||
.field("format", &"RGBA")
|
||||
.field("width", &(render_width as i32))
|
||||
.field("height", &(render_height as i32))
|
||||
.field("pixel-aspect-ratio", &gst::Fraction::new(1, 1))
|
||||
.build(),
|
||||
));
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
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)]
|
||||
pub enum InputRouting {
|
||||
@ -206,6 +208,15 @@ impl CaptureSizePreset {
|
||||
_ => 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)]
|
||||
|
||||
@ -239,9 +239,24 @@ fn refresh_eye_feed_controls(
|
||||
state.breakout_size_options(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))]
|
||||
fn eye_caps_changed(state: &LauncherState, caps: &crate::handshake::PeerCaps) -> bool {
|
||||
let next_width = caps.eye_width.unwrap_or(state.preview_source.width);
|
||||
@ -499,6 +514,12 @@ fn rebind_popout_preview(
|
||||
) {
|
||||
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))]
|
||||
|
||||
@ -28,6 +28,7 @@ pub struct SummaryWidgets {
|
||||
pub struct DisplayPaneWidgets {
|
||||
pub root: gtk::Box,
|
||||
pub stack: gtk::Stack,
|
||||
pub preview_frame: gtk::AspectFrame,
|
||||
pub picture: gtk::Picture,
|
||||
pub stream_status: gtk::Label,
|
||||
pub placeholder: gtk::Label,
|
||||
@ -41,6 +42,7 @@ pub struct DisplayPaneWidgets {
|
||||
|
||||
pub struct PopoutWindowHandle {
|
||||
pub window: gtk::ApplicationWindow,
|
||||
pub frame: gtk::AspectFrame,
|
||||
pub picture: gtk::Picture,
|
||||
pub status_label: gtk::Label,
|
||||
pub binding: PreviewBinding,
|
||||
@ -1133,6 +1135,7 @@ fn build_display_pane(title: &str, capture_path: &str) -> DisplayPaneWidgets {
|
||||
DisplayPaneWidgets {
|
||||
root,
|
||||
stack,
|
||||
preview_frame,
|
||||
picture,
|
||||
stream_status,
|
||||
placeholder,
|
||||
|
||||
@ -307,6 +307,7 @@ pub fn open_popout_window(
|
||||
let mut popouts = popouts.borrow_mut();
|
||||
popouts[monitor_id] = Some(PopoutWindowHandle {
|
||||
window: window.clone(),
|
||||
frame: frame.clone(),
|
||||
picture: picture.clone(),
|
||||
status_label: stream_status.clone(),
|
||||
binding,
|
||||
@ -1218,6 +1219,7 @@ mod tests {
|
||||
.application(&app)
|
||||
.title("Left")
|
||||
.build(),
|
||||
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||
picture: gtk::Picture::new(),
|
||||
status_label: gtk::Label::new(None),
|
||||
binding: left_binding,
|
||||
@ -1227,6 +1229,7 @@ mod tests {
|
||||
.application(&app)
|
||||
.title("Right")
|
||||
.build(),
|
||||
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||
picture: gtk::Picture::new(),
|
||||
status_label: gtk::Label::new(None),
|
||||
binding: right_binding,
|
||||
@ -1281,6 +1284,7 @@ mod tests {
|
||||
let mut slot = popouts.borrow_mut();
|
||||
slot[0] = Some(PopoutWindowHandle {
|
||||
window,
|
||||
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||
picture: gtk::Picture::new(),
|
||||
status_label: gtk::Label::new(None),
|
||||
binding: PreviewBinding::test_stub(),
|
||||
@ -1328,6 +1332,7 @@ mod tests {
|
||||
.application(&app)
|
||||
.title("Left")
|
||||
.build(),
|
||||
frame: gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false),
|
||||
picture: gtk::Picture::new(),
|
||||
status_label: gtk::Label::new(None),
|
||||
binding: PreviewBinding::test_stub(),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.11.16"
|
||||
version = "0.11.17"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -17,6 +17,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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]
|
||||
}
|
||||
|
||||
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 {
|
||||
if requested_width == 0 || requested_height == 0 {
|
||||
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]
|
||||
name = "lesavka_server"
|
||||
version = "0.11.16"
|
||||
version = "0.11.17"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user