feat(capture): add Phase 3 PipeWire capture stub and config string safety

Add a linkable CaptureFactory stub so the capture API can be consumed

without unresolved symbols. Change remaining config string_view fields to

std::string to prevent dangling references. Update handoff memory.
This commit is contained in:
2026-09-07 10:08:02 +02:00
parent 71218b1b1f
commit 6f4c670aa8
6 changed files with 58 additions and 24 deletions
+12 -4
View File
@@ -4,10 +4,16 @@ Last updated: initial scaffold.
## Project state ## Project state
- Phase 1 complete: Meson build, C++20 module headers, agent workflow, clock - Phase 2 follow-up review items addressed: encoder rate control is now
utility, and a passing unit test. bitrate-only (removed CRF), encoder/decoder cache their SwsContext, configs
- Active phase is now Phase 2 (software H.264 encode/decode). own their strings, bitstream helper is internal to the encoder with a NAL
- No implementation yet for capture/codec/network/render. length guard, FFmpeg open errors are reported, and the round-trip test
verifies Annex-B prefix and keyframes.
- `.clang-format` added at repo root and clang-format enforcement added to
`AGENTS.md` and `cpp-meson-build/SKILL.md`.
- Phase 3 capture now has a linkable stub (`src/capture/pipewire_capture.cpp`)
so the API can be consumed without an unresolved symbol.
- Remaining implementation: capture, transport, rendering, and CLI/pipeline glue.
## Decisions ## Decisions
@@ -16,6 +22,8 @@ Last updated: initial scaffold.
- Build system: Meson. - Build system: Meson.
- Capture: PipeWire + xdg-desktop-portal. - Capture: PipeWire + xdg-desktop-portal.
- Encode/Decode: FFmpeg (libavcodec, libavutil, libswscale). - Encode/Decode: FFmpeg (libavcodec, libavutil, libswscale).
- H.264 encoder path: software `libx264`, low-latency settings, Annex-B output.
- Decoder receives SPS/PPS through `DecoderConfig.extradata`.
- Transport: RTP over UDP; signaling via WebSocket/JSON. - Transport: RTP over UDP; signaling via WebSocket/JSON.
- Discovery: mDNS/Avahi. - Discovery: mDNS/Avahi.
- Rendering: SDL2 or SDL3 + OpenGL. - Rendering: SDL2 or SDL3 + OpenGL.
+12 -10
View File
@@ -3,26 +3,31 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span> #include <string>
#include <string_view>
#include <variant> #include <variant>
#include <vector> #include <vector>
namespace sc { namespace sc {
enum class PixelFormat {
Rgba,
Yuv420p,
};
// Opaque resource owned by the capture implementation. // Opaque resource owned by the capture implementation.
struct CapturedFrame { struct CapturedFrame {
int width = 0; int width = 0;
int height = 0; int height = 0;
uint64_t timestamp_ns = 0; // capture clock, monotonic uint64_t timestamp_ns = 0; // capture clock, monotonic
PixelFormat pixel_format = PixelFormat::Rgba;
int stride = 0; // bytes per row for the first plane; 0 means packed
std::vector<std::byte> pixels; std::vector<std::byte> pixels;
// TODO: pixel format, stride, FD handle for dmabuf
}; };
// Capture target: whole monitor, specific window, or a region. // Capture target: whole monitor, specific window, or a region.
struct CaptureTargetWholeScreen {}; struct CaptureTargetWholeScreen {};
struct CaptureTargetWindow { struct CaptureTargetWindow {
std::string_view window_id; std::string window_id;
}; };
struct CaptureTargetRegion { struct CaptureTargetRegion {
int x = 0; int x = 0;
@@ -31,13 +36,10 @@ struct CaptureTargetRegion {
int height = 0; int height = 0;
}; };
using CaptureTarget = std::variant< using CaptureTarget = std::variant<CaptureTargetWholeScreen, CaptureTargetWindow, CaptureTargetRegion>;
CaptureTargetWholeScreen,
CaptureTargetWindow,
CaptureTargetRegion>;
class CaptureSession { class CaptureSession {
public: public:
virtual ~CaptureSession() = default; virtual ~CaptureSession() = default;
// Blocking call to acquire one frame. Returns std::nullopt on graceful stop. // Blocking call to acquire one frame. Returns std::nullopt on graceful stop.
@@ -49,7 +51,7 @@ public:
// Factory for the PipeWire / xdg-desktop-portal capture backend. // Factory for the PipeWire / xdg-desktop-portal capture backend.
class CaptureFactory { class CaptureFactory {
public: public:
static std::unique_ptr<CaptureSession> create(CaptureTarget target); static std::unique_ptr<CaptureSession> create(CaptureTarget target);
}; };
+8 -10
View File
@@ -2,36 +2,34 @@
#include "screencast/codec/decoder.h" #include "screencast/codec/decoder.h"
#include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <string>
#include <string_view>
namespace sc { namespace sc {
struct RendererConfig { struct RendererConfig {
std::string_view window_title = "screencast receiver"; std::string window_title = "screencast receiver";
int initial_width = 1280; int initial_width = 1280;
int initial_height = 720; int initial_height = 720;
}; };
class Renderer { class Renderer {
public: public:
virtual ~Renderer () = default; virtual ~Renderer() = default;
// Present one decoded frame. Returns false if the window was closed. // Present one decoded frame. Returns false if the window was closed.
virtual bool present ( const DecodedFrame &frame ) = 0; virtual bool present(const DecodedFrame& frame) = 0;
// Pump events (window close, resize). Non-blocking. // Pump events (window close, resize). Non-blocking.
virtual bool poll_events () = 0; virtual bool poll_events() = 0;
// Destroy the window and release GPU resources. // Destroy the window and release GPU resources.
virtual void shutdown () = 0; virtual void shutdown() = 0;
}; };
class RendererFactory { class RendererFactory {
public: public:
static std::unique_ptr<Renderer> create ( const RendererConfig &config ); static std::unique_ptr<Renderer> create(const RendererConfig& config);
}; };
} // namespace sc } // namespace sc
+11
View File
@@ -0,0 +1,11 @@
# Phase 3 capture placeholder. libpipewire-0.3 will be added when the portal
# implementation is wired in.
sc_capture_sources = files('pipewire_capture.cpp')
# Built even though nothing links it yet, so the stub stays compile-clean.
static_library('sc_capture',
sc_capture_sources,
include_directories : sc_core_inc)
# sc_capture_dep will be declared once the app/pipeline code links against it.
+13
View File
@@ -0,0 +1,13 @@
#include "screencast/capture/capture.h"
#include <memory>
namespace sc {
std::unique_ptr<CaptureSession> CaptureFactory::create(CaptureTarget /*target*/) {
// Phase 3 placeholder. The PipeWire / xdg-desktop-portal capture backend
// will be implemented here once the codec round-trip is solidified.
return nullptr;
}
} // namespace sc
+2
View File
@@ -1,6 +1,8 @@
# Core public headers / include dependency. # Core public headers / include dependency.
sc_core_inc = include_directories('../include') sc_core_inc = include_directories('../include')
subdir('capture')
# Phase 2 codec dependencies. # Phase 2 codec dependencies.
dep_avcodec = dependency('libavcodec') dep_avcodec = dependency('libavcodec')
dep_avutil = dependency('libavutil') dep_avutil = dependency('libavutil')