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:
+12
-4
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user