From ce81fb7ad324bb88a04df34efc3941c1026a0d16 Mon Sep 17 00:00:00 2001 From: Jonathan Lindstrom Date: Sun, 5 Jul 2026 19:40:23 -0400 Subject: [PATCH 1/3] CameraView: consider raw caps when picking photo resolution Cameras that expose no image/jpeg caps (e.g. the PCIe FaceTime HD in 2015 MacBook Pros using the out-of-tree facetimehd driver) always fell back to 640x480, and photos from 16:9-only sensors were stretched to 4:3. Scan video/x-raw structures too so photos use the sensor's native resolution. Co-Authored-By: Claude Fable 5 --- src/Widgets/CameraView.vala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Widgets/CameraView.vala b/src/Widgets/CameraView.vala index 765c04b53..1a36834b4 100644 --- a/src/Widgets/CameraView.vala +++ b/src/Widgets/CameraView.vala @@ -211,7 +211,10 @@ public class Camera.Widgets.CameraView : Gtk.Box { for (uint i = 0; i < caps.get_size (); i++) { unowned var s = caps.get_structure (i); - if (s.get_name () == "image/jpeg") { + // Consider raw modes too: some cameras (e.g. the PCIe FaceTime + // HD on 2015 MacBook Pros) offer no JPEG caps at all, and the + // 640×480 fallback distorts photos from 16:9-only sensors + if (s.get_name () == "image/jpeg" || s.get_name () == "video/x-raw") { int w, h; s.get_int ("width", out w); s.get_int ("height", out h); From f9a2b9b6a72aee5a9fc9a2b85a70fa584fd0aff4 Mon Sep 17 00:00:00 2001 From: Jonathan Lindstrom Date: Sun, 5 Jul 2026 19:40:53 -0400 Subject: [PATCH 2/3] CameraView: configure vp8enc for realtime capture vp8enc's defaults (256 kbps target bitrate, best-quality deadline) cannot encode HD webcam streams in real time. The encoder falls behind, the leaky preview queue drops frames, and recordings come out choppy at extremely low quality (measured ~80 kbps for 720p30). Use the realtime deadline, spread work across the available CPU cores, and scale the target bitrate with the frame size (~3.7 Mbps at 720p). Co-Authored-By: Claude Fable 5 --- src/Widgets/CameraView.vala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Widgets/CameraView.vala b/src/Widgets/CameraView.vala index 1a36834b4..e6e2e33cc 100644 --- a/src/Widgets/CameraView.vala +++ b/src/Widgets/CameraView.vala @@ -350,6 +350,14 @@ public class Camera.Widgets.CameraView : Gtk.Box { var encoder = Gst.ElementFactory.make ("vp8enc", null); if (encoder == null) { missing_messages += Gst.PbUtils.missing_element_installer_detail_new ("vp8enc"); + } else { + // vp8enc defaults (256 kbps target, exhaustive-quality deadline) + // cannot encode HD in real time: frames drop and quality collapses + encoder["deadline"] = (int64) 1; + encoder["cpu-used"] = 4; + encoder["threads"] = (int) GLib.get_num_processors (); + encoder["keyframe-max-dist"] = 60; + encoder["target-bitrate"] = picture_width * picture_height * 4; } var muxer = Gst.ElementFactory.make ("webmmux", null); From 0c1c28e0aca7efe35d4652fa13ce864c124b4fe0 Mon Sep 17 00:00:00 2001 From: Jonathan Lindstrom Date: Sun, 5 Jul 2026 19:41:12 -0400 Subject: [PATCH 3/3] CameraView: handle missing gtk4paintablesink Every element in the recording path reports a missing-plugin message, but the preview sink was used unchecked. On systems without the GTK4 GStreamer plugin the preview silently stayed black while photos and recording kept working. Prompt for plugin installation and surface the existing 'Unable To View Camera' dialog instead. Co-Authored-By: Claude Fable 5 --- src/Widgets/CameraView.vala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Widgets/CameraView.vala b/src/Widgets/CameraView.vala index e6e2e33cc..70b1a7ac0 100644 --- a/src/Widgets/CameraView.vala +++ b/src/Widgets/CameraView.vala @@ -249,6 +249,14 @@ public class Camera.Widgets.CameraView : Gtk.Box { videorate.drop_only = true; dynamic Gst.Element gtksink = Gst.ElementFactory.make ("gtk4paintablesink", "sink"); + if (gtksink == null) { + // Without this check the preview silently stays black while + // capture keeps working, since the preview branch dead-ends + // behind the leaky queue + string[] messages = { Gst.PbUtils.missing_element_installer_detail_new ("gtk4paintablesink") }; + Gst.PbUtils.install_plugins_async (messages, null, (result) => {}); + throw new IOError.NOT_FOUND ("Missing GStreamer element \"gtk4paintablesink\""); + } pipeline.add (gtksink); pipeline.get_by_name ("videoscale").link (gtksink);