Enable true 60fps capture and GUI repaint rate with automatic UVC high-framerate unlock

This commit is contained in:
Lucas Oskorep committed 2026-10-21 01:02:42 -04:00
1 parent 51b30cbd79
commit ef641ac31b
7 files changed
+191 -105

No files matched your search

+35 -9
View File
@@ -86,6 +86,12 @@ impl Insta360App {
fn auto_select_insta360_video_feed(&mut self) {
let (w, h) = self.state.format_preset.dimensions();
let fps = self.state.format_preset.fps();
if (fps > 30 || self.state.format_preset.is_vertical())
&& !self.state.vertical_resolution_mode
{
self.state.vertical_resolution_mode = true;
let _ = self.controller.set_vertical_screen(true);
}
self.video_engine
.set_capture_format(w as usize, h as usize, fps);
@@ -604,7 +610,13 @@ impl Insta360App {
{
let (w, h) = preset.dimensions();
let fps = preset.fps();
self.state.vertical_resolution_mode = preset.is_vertical();
if (fps > 30 || preset.is_vertical())
&& !self.state.vertical_resolution_mode
{
self.video_engine.stop_physical_capture();
self.state.vertical_resolution_mode = true;
let _ = self.controller.set_vertical_screen(true);
}
self.state.low_resolution_mode = w == 640;
self.video_engine.set_capture_format(w as usize, h as usize, fps);
self.state
@@ -840,7 +852,7 @@ impl Insta360App {
}
// Live video feed rendering with aspect ratio preservation
let target_aspect = if self.state.vertical_resolution_mode {
let target_aspect = if self.state.format_preset.is_vertical() {
9.0_f32 / 16.0_f32
} else {
16.0_f32 / 9.0_f32
@@ -2085,19 +2097,33 @@ impl Insta360App {
impl eframe::App for Insta360App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Continuous repaint loop for smooth 30fps live video preview
if self.state.preview_open {
ctx.request_repaint_after(Duration::from_millis(33));
}
// Keep capture resolution synced with selected format preset
// Keep capture resolution and frame rate synced with selected format preset
let (target_w, target_h) = self.state.format_preset.dimensions();
let target_fps = self.state.format_preset.fps();
if self.video_engine.capture_dimensions() != (target_w as usize, target_h as usize) {
if self.video_engine.capture_dimensions() != (target_w as usize, target_h as usize)
|| self.video_engine.capture_fps() != target_fps
{
if (target_fps > 30 || self.state.format_preset.is_vertical())
&& !self.state.vertical_resolution_mode
{
self.video_engine.stop_physical_capture();
self.state.vertical_resolution_mode = true;
let _ = self.controller.set_vertical_screen(true);
}
self.video_engine
.set_capture_format(target_w as usize, target_h as usize, target_fps);
}
// Continuous repaint loop matching active capture framerate (16ms for 60fps, 33ms for 30fps)
if self.state.preview_open {
let repaint_ms = if self.video_engine.capture_fps() >= 50 {
16
} else {
33
};
ctx.request_repaint_after(Duration::from_millis(repaint_ms));
}
// 1. One-time hardware initialization once the physical video stream is open and delivering frames
if self.video_engine.has_live_physical_frame() && !self.state.stream_initialized {
self.state.stream_initialized = true;
+4 -4
View File
@@ -883,15 +883,15 @@ fn render_more_tab<T: UvcControlTransport>(
ui,
"Portrait Resolution and High Frame Rate",
None,
Some("Enable 9:16 portrait resolution output."),
Some("Unlock 60fps high frame rate and 9:16 portrait resolution in UVC."),
&mut state.vertical_resolution_mode,
!state.is_recording,
theme,
) {
let _ = ctrl.set_vertical_screen(state.vertical_resolution_mode);
if state.vertical_resolution_mode {
state.format_preset = crate::state::VideoFormatPreset::Vertical1080x1920p30;
} else {
if !state.vertical_resolution_mode
&& (state.format_preset.is_vertical() || state.format_preset.fps() > 30)
{
state.format_preset = crate::state::VideoFormatPreset::Fhd1080p30;
}
}
+2 -1
View File
@@ -651,7 +651,8 @@ impl CameraAppState {
self.smart_adjustment = p.smart_adjustment;
self.horizontal_correction = p.horizontal_correction;
self.roll_deg = p.roll_deg.clamp(-1.0, 1.0);
self.vertical_resolution_mode = p.vertical_resolution_mode;
self.vertical_resolution_mode =
p.vertical_resolution_mode || p.format_preset.fps() > 30 || p.format_preset.is_vertical();
self.forced_vertical_lock = p.forced_vertical_lock;
self.low_resolution_mode = p.low_resolution_mode;
}
+31 -9
View File
@@ -139,6 +139,11 @@ impl VideoPreviewEngine {
(self.capture_width, self.capture_height)
}
/// Returns the currently configured capture frame rate (`fps`).
pub fn capture_fps(&self) -> u32 {
self.capture_fps
}
/// List of discovered hardware capture devices.
pub fn available_capture_devices(&self) -> &[String] {
&self.available_devices
@@ -301,21 +306,32 @@ impl VideoPreviewEngine {
let frame_size = out_w * out_h * 3;
let mut buffer = vec![0u8; frame_size];
for attempt in 0..2 {
for attempt in 0..8 {
if stop_flag.load(Ordering::Relaxed) {
break;
}
if attempt > 0 {
std::thread::sleep( std::time::Duration::from_millis(400));
if stop_flag.load(Ordering::Relaxed) {
break;
}
}
let mut cmd = Command::new("ffmpeg");
#[cfg(target_os = "macos")]
{
// Insta360 Link AVFoundation natively supports 3840x2160, 1920x1080, 1280x720 at up to 30fps
// Insta360 Link AVFoundation natively supports 3840x2160, 1920x1080, 1280x720, 1080x1920
// in '420v' (nv12) and '2vuy' (uyvy422). Using nv12 matches both the official
// Insta360 Link Controller (`OpenDevice` at 0x10008770c) and Chrome/Google Meet.
let hw_fps = fps.min(30).to_string();
let hw_fps = if attempt < 6 {
fps.to_string()
} else {
fps.min(30).to_string()
};
let hw_size = match (out_w, out_h) {
(3840, 2160) => "3840x2160",
(1280, 720) => "1280x720",
(1080, 1920) if attempt < 6 => "1080x1920",
_ => "1920x1080",
};
cmd.args([
@@ -327,7 +343,7 @@ impl VideoPreviewEngine {
"-pixel_format",
"nv12",
]);
if attempt == 0 {
if attempt < 7 {
cmd.args(["-framerate", &hw_fps, "-video_size", hw_size]);
} else {
cmd.args(["-framerate", "30", "-video_size", "1280x720"]);
@@ -351,14 +367,19 @@ impl VideoPreviewEngine {
#[cfg(target_os = "linux")]
{
let hw_fps = fps.min(30).to_string();
let hw_fps = if attempt < 6 {
fps.to_string()
} else {
fps.min(30).to_string()
};
let hw_size = match (out_w, out_h) {
(3840, 2160) => "3840x2160",
(1280, 720) => "1280x720",
(1080, 1920) if attempt < 6 => "1080x1920",
_ => "1920x1080",
};
cmd.args(["-hide_banner", "-loglevel", "error", "-f", "v4l2"]);
if attempt == 0 {
if attempt < 7 {
cmd.args([
"-input_format",
"mjpeg",
@@ -543,13 +564,14 @@ impl VideoPreviewEngine {
}
}
} else {
let is_vert = state.format_preset.is_vertical();
let (out_w, out_h) = if state.low_resolution_mode {
if state.vertical_resolution_mode {
if is_vert {
(270, 480)
} else {
(480, 270)
}
} else if state.vertical_resolution_mode {
} else if is_vert {
(360, 640)
} else {
(640, 360)
@@ -1110,7 +1132,7 @@ impl VideoPreviewEngine {
let w = self.capture_width;
let h = self.capture_height;
let fps = self.capture_fps.min(30).max(15);
let fps = self.capture_fps.clamp(15, 60);
let size_arg = format!("{w}x{h}");
let fps_arg = fps.to_string();