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
- Phase 1 complete: Meson build, C++20 module headers, agent workflow, clock
utility, and a passing unit test.
- Active phase is now Phase 2 (software H.264 encode/decode).
- No implementation yet for capture/codec/network/render.
- Phase 2 follow-up review items addressed: encoder rate control is now
bitrate-only (removed CRF), encoder/decoder cache their SwsContext, configs
own their strings, bitstream helper is internal to the encoder with a NAL
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
@@ -16,6 +22,8 @@ Last updated: initial scaffold.
- Build system: Meson.
- Capture: PipeWire + xdg-desktop-portal.
- 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.
- Discovery: mDNS/Avahi.
- Rendering: SDL2 or SDL3 + OpenGL.
+12 -10
View File
@@ -3,26 +3,31 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <string_view>
#include <string>
#include <variant>
#include <vector>
namespace sc {
enum class PixelFormat {
Rgba,
Yuv420p,
};
// Opaque resource owned by the capture implementation.
struct CapturedFrame {
int width = 0;
int height = 0;
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;
// TODO: pixel format, stride, FD handle for dmabuf
};
// Capture target: whole monitor, specific window, or a region.
struct CaptureTargetWholeScreen {};
struct CaptureTargetWindow {
std::string_view window_id;
std::string window_id;
};
struct CaptureTargetRegion {
int x = 0;
@@ -31,13 +36,10 @@ struct CaptureTargetRegion {
int height = 0;
};
using CaptureTarget = std::variant<
CaptureTargetWholeScreen,
CaptureTargetWindow,
CaptureTargetRegion>;
using CaptureTarget = std::variant<CaptureTargetWholeScreen, CaptureTargetWindow, CaptureTargetRegion>;
class CaptureSession {
public:
public:
virtual ~CaptureSession() = default;
// 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.
class CaptureFactory {
public:
public:
static std::unique_ptr<CaptureSession> create(CaptureTarget target);
};
+8 -10
View File
@@ -2,36 +2,34 @@
#include "screencast/codec/decoder.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string_view>
#include <string>
namespace sc {
struct RendererConfig {
std::string_view window_title = "screencast receiver";
std::string window_title = "screencast receiver";
int initial_width = 1280;
int initial_height = 720;
};
class Renderer {
public:
virtual ~Renderer () = default;
virtual ~Renderer() = default;
// 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.
virtual bool poll_events () = 0;
virtual bool poll_events() = 0;
// Destroy the window and release GPU resources.
virtual void shutdown () = 0;
virtual void shutdown() = 0;
};
class RendererFactory {
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.
sc_core_inc = include_directories('../include')
subdir('capture')
# Phase 2 codec dependencies.
dep_avcodec = dependency('libavcodec')
dep_avutil = dependency('libavutil')