diff --git a/.agents/MEMORY.md b/.agents/MEMORY.md index 2344a61..f155786 100644 --- a/.agents/MEMORY.md +++ b/.agents/MEMORY.md @@ -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. diff --git a/include/screencast/capture/capture.h b/include/screencast/capture/capture.h index b81d0a2..2b5ac94 100644 --- a/include/screencast/capture/capture.h +++ b/include/screencast/capture/capture.h @@ -3,26 +3,31 @@ #include #include #include -#include -#include +#include #include #include 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 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; 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 create(CaptureTarget target); }; diff --git a/include/screencast/render/renderer.h b/include/screencast/render/renderer.h index 962e8c4..61bad66 100644 --- a/include/screencast/render/renderer.h +++ b/include/screencast/render/renderer.h @@ -2,36 +2,34 @@ #include "screencast/codec/decoder.h" -#include #include -#include -#include +#include 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 create ( const RendererConfig &config ); + static std::unique_ptr create(const RendererConfig& config); }; -} // namespace sc +} // namespace sc diff --git a/src/capture/meson.build b/src/capture/meson.build new file mode 100644 index 0000000..9a26209 --- /dev/null +++ b/src/capture/meson.build @@ -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. diff --git a/src/capture/pipewire_capture.cpp b/src/capture/pipewire_capture.cpp new file mode 100644 index 0000000..9ff5b49 --- /dev/null +++ b/src/capture/pipewire_capture.cpp @@ -0,0 +1,13 @@ +#include "screencast/capture/capture.h" + +#include + +namespace sc { + +std::unique_ptr 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 diff --git a/src/meson.build b/src/meson.build index b7527ba..f1dd981 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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')