Scaffold C++20 screencast project with Meson, agent workflow, and phase plan
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# Project Memory — screen_cast
|
||||
|
||||
Last updated: initial scaffold.
|
||||
|
||||
## Project state
|
||||
|
||||
- Empty project scaffolded with Meson build, C++20, module headers, and agent
|
||||
workflow files.
|
||||
- No implementation yet; headers are placeholders.
|
||||
- Phase plan is in `docs/PHASES.md`. Agents are now explicitly instructed in
|
||||
`AGENTS.md` to read `docs/PHASES.md` before planning and not to start work
|
||||
belonging to a later phase unless requested.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Language: C++20 with explicit modern-C++ guidelines in `AGENTS.md` and
|
||||
`cpp-meson-build/SKILL.md`.
|
||||
- Build system: Meson.
|
||||
- Capture: PipeWire + xdg-desktop-portal.
|
||||
- Encode/Decode: FFmpeg (libavcodec, libavutil, libswscale).
|
||||
- Transport: RTP over UDP; signaling via WebSocket/JSON.
|
||||
- Discovery: mDNS/Avahi.
|
||||
- Rendering: SDL2 or SDL3 + OpenGL.
|
||||
- Namespace: `sc`.
|
||||
|
||||
## Active blockers
|
||||
|
||||
None.
|
||||
|
||||
## Open questions
|
||||
|
||||
- GUI framework (Qt6 vs. none / CLI only) — deferred to later phase.
|
||||
- Hardware acceleration strategy (VAAPI / Vulkan Video / NVENC) — evaluate after
|
||||
software encode path works.
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: agent-memory
|
||||
description: |
|
||||
Persistent handoff memory for multi-session agent work on the screen_cast
|
||||
project. Read this skill at the start of every new agent conversation so the
|
||||
current thread can bootstrap context from the repository rather than from chat
|
||||
history.
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
## Always read at the start of a new conversation
|
||||
|
||||
1. `AGENTS.md` — mandatory project workflow.
|
||||
2. This skill (`agent-memory/SKILL.md`).
|
||||
3. `.agents/MEMORY.md` — current handoff log.
|
||||
4. Any domain-specific `RUNBOOK.md` relevant to the task (e.g.
|
||||
`docs/RUNBOOK.md`).
|
||||
5. All other skills applicable to the task (see `AGENTS.md` skill selection).
|
||||
|
||||
## Purpose
|
||||
|
||||
Zed agent threads do not share conversation history. This project therefore
|
||||
keeps the shared state in the repository itself:
|
||||
|
||||
- `.agents/MEMORY.md` — rolling handoff log: current focus, completed work,
|
||||
open blockers, decisions, files that matter.
|
||||
- `<domain>/RUNBOOK.md` — operational memory for recurring workflows
|
||||
(build/CI, capture validation, networking smoke tests).
|
||||
|
||||
Both files are ordinary markdown under git, so their history is preserved.
|
||||
|
||||
## After significant work
|
||||
|
||||
Update `.agents/MEMORY.md`:
|
||||
|
||||
- **Current focus**: one-line summary of what the thread was working on.
|
||||
- **Completed**: concrete outcomes, file paths, commits.
|
||||
- **Open issues / blockers**: anything unresolved at the end of the thread.
|
||||
- **Decisions & conventions**: choices that future threads must respect.
|
||||
- **Files that matter right now**: paths the next thread should read first.
|
||||
|
||||
Update domain `RUNBOOK.md` when the task reveals a reusable observation:
|
||||
|
||||
- non-obvious build or dependency workarounds;
|
||||
- validation steps that caught errors;
|
||||
- commands or snippets that should be reused.
|
||||
|
||||
## When starting a new thread
|
||||
|
||||
Paste a brief handoff if helpful, but **do not rely on it**. Always verify the
|
||||
actual current state from the memory files, git log, and the relevant code.
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
name: cpp-meson-build
|
||||
description: |
|
||||
Guidelines for C++20 development and Meson build management in the
|
||||
screen_cast project. Read before changing source files, build files,
|
||||
dependencies, or toolchain configuration.
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
# cpp-meson-build
|
||||
|
||||
Guidelines for C++20 development and Meson build management in the `screen_cast`
|
||||
project.
|
||||
|
||||
## Scope
|
||||
|
||||
Use this skill for:
|
||||
|
||||
- Adding or changing C++ source files, headers, or namespaces.
|
||||
- Modifying `meson.build` files, compiler flags, or targets.
|
||||
- Adding or updating dependencies (system libraries, pkg-config, subprojects).
|
||||
- Toolchain, sanitizer, static-analysis, or formatter configuration.
|
||||
|
||||
## Meson conventions
|
||||
|
||||
- Keep `meson.build` files declarative and readable.
|
||||
- Prefer `dependency()` with `pkg-config` names over manual `-l` flags.
|
||||
- Pin required C++ standard: `cpp_std = 'c++20'`.
|
||||
- Put one target per `meson.build` file where practical.
|
||||
- Use `include_directories('include')` for the public API headers.
|
||||
- Declare unit tests with `test()` and keep them deterministic.
|
||||
|
||||
## Dependency verification
|
||||
|
||||
Before adding a new dependency:
|
||||
|
||||
- verify the pkg-config file exists on the target system (e.g.
|
||||
`pkg-config --exists libavcodec`);
|
||||
- check that the required headers compile;
|
||||
- update `meson.build` and this skill's `references/` if needed.
|
||||
|
||||
## Error handling
|
||||
|
||||
- Prefer `std::expected<T,E>`, `std::optional<T>`, or `std::error_code` over
|
||||
exceptions for recoverable failures.
|
||||
- Use RAII wrappers; avoid raw owning pointers.
|
||||
- Keep headers minimal and forward-declare where possible.
|
||||
|
||||
## Modern C++ guidelines
|
||||
|
||||
All project code must use C++20 idioms:
|
||||
|
||||
- **Ownership**: use `std::unique_ptr` and `std::shared_ptr`; no raw `new`/`delete`
|
||||
or raw owning pointers.
|
||||
- **Optional values**: use `std::optional<T>` instead of sentinel values or
|
||||
out-parameters.
|
||||
- **Fallible operations**: use `std::expected<T,E>` or `std::error_code` for
|
||||
recoverable errors; avoid exceptions for control flow.
|
||||
- **Views**: use `std::span<T>` for non-owning buffers and `std::string_view`
|
||||
for read-only strings.
|
||||
- **Compile-time**: prefer `constexpr`/`consteval` where possible.
|
||||
- **Type safety**: prefer `enum class`, `std::variant`, and `std::optional`
|
||||
over bare integers or bool flags.
|
||||
- **Concurrency**: prefer `std::jthread`, `std::stop_token`, and standard
|
||||
synchronization primitives.
|
||||
- **Containers**: use standard containers; avoid C arrays and raw buffers.
|
||||
- **Formatting/time**: use `<format>` / `std::format` and `<chrono>`.
|
||||
- **Algorithms**: prefer `std::ranges` over hand-rolled loops where it
|
||||
improves clarity.
|
||||
- **Callables**: avoid `std::function` unless type erasure is required.
|
||||
- **Concepts**: use `requires` clauses to express interface contracts where
|
||||
beneficial.
|
||||
|
||||
Reject C-style patterns (`printf`, `sprintf`, manual memory management with
|
||||
`malloc`/`free`, raw arrays) when a standard-library equivalent exists.
|
||||
|
||||
## Useful commands
|
||||
|
||||
```sh
|
||||
meson setup build
|
||||
meson compile -C build
|
||||
meson test -C build --print-errorlogs
|
||||
meson configure build
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- `meson.build` (project root)
|
||||
- `meson_options.txt` (if present)
|
||||
- `docs/ARCHITECTURE.md`
|
||||
- `docs/PHASES.md`
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: linux-multimedia-capture
|
||||
description: |
|
||||
Guidelines for Linux screen/audio capture, FFmpeg encoding/decoding, and
|
||||
hardware acceleration in the screen_cast project.
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
# linux-multimedia-capture
|
||||
|
||||
Guidelines for Linux screen/audio capture, FFmpeg encoding/decoding, and
|
||||
hardware acceleration in the `screen_cast` project.
|
||||
|
||||
## Scope
|
||||
|
||||
Use this skill for:
|
||||
|
||||
- Screen or window capture (PipeWire, xdg-desktop-portal D-Bus interfaces).
|
||||
- Audio capture or playback (PipeWire audio nodes).
|
||||
- Video encoding/decoding with FFmpeg/libavcodec.
|
||||
- Pixel format conversion, scaling (libswscale, libavutil).
|
||||
- Hardware acceleration (VAAPI, Vulkan Video, NVENC) via FFmpeg.
|
||||
|
||||
## Capture
|
||||
|
||||
- Use PipeWire with the `xdg-desktop-portal` screen-cast portal for Wayland and
|
||||
X11 compatibility.
|
||||
- Prefer screencast source types that request monitor, window, or virtual.
|
||||
- Treat portal session tokens, node IDs, and stream FDs as owned resources;
|
||||
close and destroy them on error paths.
|
||||
|
||||
## Encoding / decoding
|
||||
|
||||
- Use `AVCodecContext`, `AVFrame`, and `AVPacket` through RAII wrappers.
|
||||
- Validate codec capabilities (e.g. supported pixel formats, thread counts).
|
||||
- For H.264 baseline path use `libx264` or `h264_vaapi` when available.
|
||||
- Parse NAL units carefully before packetizing; reject malformed data.
|
||||
|
||||
## Hardware acceleration
|
||||
|
||||
- Probe available encoders with `avcodec_find_encoder_by_name()`.
|
||||
- Prefer VAAPI on Intel/AMD, NVENC on NVIDIA, Vulkan Video where supported.
|
||||
- Keep a software fallback path for compatibility.
|
||||
|
||||
## Security / safety
|
||||
|
||||
- Bounds-check all buffer sizes before copying into packets.
|
||||
- Validate resolution, stride, and pixel-format assumptions from the portal.
|
||||
- Avoid exposing capture tokens or session handles in logs.
|
||||
|
||||
## References
|
||||
|
||||
- PipeWire API docs and `libpipewire-0.3`
|
||||
- `libportal` (optional wrapper for the desktop portal)
|
||||
- FFmpeg documentation: `doc/APIchanges`, `doc/examples`
|
||||
- `docs/ARCHITECTURE.md`
|
||||
- `docs/PHASES.md`
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: rtp-networking
|
||||
description: |
|
||||
Guidelines for RTP/UDP transport, signaling, discovery, NAT traversal, and
|
||||
encryption in the screen_cast project.
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
# rtp-networking
|
||||
|
||||
Guidelines for RTP/UDP transport, signaling, discovery, NAT traversal, and
|
||||
encryption in the `screen_cast` project.
|
||||
|
||||
## Scope
|
||||
|
||||
Use this skill for:
|
||||
|
||||
- RTP packetization, depacketization, and sequencing.
|
||||
- UDP socket I/O and async event loops.
|
||||
- Peer discovery (mDNS/DNS-SD).
|
||||
- Session signaling and control channels.
|
||||
- Optional SRTP/DTLS or ICE-lite/NAT traversal.
|
||||
|
||||
## Transport
|
||||
|
||||
- Default transport is **RTP over IPv4/IPv6 UDP**.
|
||||
- Use a small, project-specific RTP header parser/serializer (12-byte base
|
||||
header plus extensions if needed).
|
||||
- Fragment large NAL units across RTP packets using FU-A fragmentation for
|
||||
H.264.
|
||||
|
||||
## Resilience
|
||||
|
||||
- Start without retransmission; add **NACK / PLI** once the baseline path works.
|
||||
- Keep a jitter buffer on the receiver to absorb network jitter without adding
|
||||
excessive latency.
|
||||
- Sequence numbers drive depacketization; detect loss and request key frames on
|
||||
large gaps.
|
||||
|
||||
## Discovery
|
||||
|
||||
- Use **Avahi** (`avahi-client`) for mDNS/DNS-SD service announcements.
|
||||
- Service name example: `_screencast._tcp` for signaling, `_screencast-rtp._udp`
|
||||
for media.
|
||||
|
||||
## Signaling
|
||||
|
||||
- Control channel is JSON over WebSocket or plain TCP.
|
||||
- Negotiate at least: codec, resolution, frame rate, UDP endpoints, session ID.
|
||||
- Keep signaling independent of media transport so either can evolve.
|
||||
|
||||
## Security (future)
|
||||
|
||||
- SRTP key exchange via DTLS or pre-shared keys over the signaling channel.
|
||||
- Do not hardcode keys; rotate per session.
|
||||
|
||||
## Async I/O
|
||||
|
||||
- Prefer an existing event loop library (e.g. `asio`, `libuv`) over raw threads
|
||||
and `select()`.
|
||||
- Keep network and capture threads separated with lock-free queues where
|
||||
practical.
|
||||
|
||||
## References
|
||||
|
||||
- RFC 3550 (RTP), RFC 6184 (H.264 RTP payload)
|
||||
- Avahi client documentation
|
||||
- `docs/ARCHITECTURE.md`
|
||||
- `docs/PHASES.md`
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
name: screencast-app
|
||||
description: |
|
||||
Overall screencast application architecture, sender/receiver modes,
|
||||
protocol negotiation, and integration decisions in the screen_cast project.
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
# screencast-app
|
||||
|
||||
Overall screencast application architecture, sender/receiver modes, protocol
|
||||
negotiation, and integration decisions in the `screen_cast` project.
|
||||
|
||||
## Scope
|
||||
|
||||
Use this skill for:
|
||||
|
||||
- Designing sender/receiver flows and state machines.
|
||||
- Deciding on CLI/GUI entry points and common application lifecycle.
|
||||
- Session negotiation formats and codec selection.
|
||||
- Integrating capture, encode, network, decode, and render modules.
|
||||
- Defining public API boundaries and module ownership.
|
||||
|
||||
## High-level architecture
|
||||
|
||||
```
|
||||
Sender: Capture → Encode → Packetize → RTP/UDP
|
||||
Receiver: RTP/UDP → Depacketize → Decode → Render
|
||||
Control: Signaling (WebSocket/JSON) + Discovery (mDNS)
|
||||
```
|
||||
|
||||
- The same binary supports both `--send` and `--receive` modes.
|
||||
- Each mode owns a pipeline object that wires the modules together.
|
||||
- Keep modules isolated; each module owns its thread(s) and exposes a small
|
||||
boundary to the pipeline.
|
||||
|
||||
## Pipeline rules
|
||||
|
||||
- Frame ownership is transferred with `std::unique_ptr` or shared frame pool.
|
||||
- Timestamp everything in the capture clock domain and carry RTP timestamps
|
||||
through the pipeline.
|
||||
- On sender error, drain packets in flight before shutting down.
|
||||
- On receiver error, request a key frame (PLI) or restart decoder if necessary.
|
||||
|
||||
## Protocol negotiation
|
||||
|
||||
Minimum negotiated fields:
|
||||
|
||||
- `codec`: `h264` initially.
|
||||
- `width`, `height`, `frame_rate`.
|
||||
- `rtp_endpoint`: `host:port` for media.
|
||||
- `signaling_endpoint`: host/port for control (optional if already connected).
|
||||
- `session_id`: unique string to correlate media and control.
|
||||
|
||||
## CLI / entry points
|
||||
|
||||
- `screencast --send [--target monitor|window]`
|
||||
- `screencast --receive [--peer-address ADDR]`
|
||||
- Optional `--signaling-url`, `--codec`, `--bitrate`, `--hwaccel` flags.
|
||||
|
||||
## GUI
|
||||
|
||||
- GUI is optional and lives in a separate target/library.
|
||||
- The core library must be usable from CLI and tests without a GUI.
|
||||
|
||||
## References
|
||||
|
||||
- `docs/ARCHITECTURE.md`
|
||||
- `docs/PHASES.md` — must be consulted before starting implementation; do not
|
||||
work on a later phase unless the current phase is complete or the user
|
||||
explicitly requests it.
|
||||
- Other domain skills: `linux-multimedia-capture`, `rtp-networking`,
|
||||
`cpp-meson-build`
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Build directories
|
||||
build/
|
||||
build-*/
|
||||
*.build/
|
||||
|
||||
# Meson
|
||||
meson-logs/
|
||||
meson-private/
|
||||
.subproject/
|
||||
|
||||
# Generated
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.dylib
|
||||
*.exe
|
||||
compile_commands.json
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,264 @@
|
||||
# AGENTS.md — Screencast (Linux) Agent Workflow
|
||||
|
||||
This document governs how Zed agents work in the `screen_cast` project. It is mandatory reading before any non-trivial implementation task.
|
||||
|
||||
## Agent memory
|
||||
|
||||
Before starting work in a new agent conversation:
|
||||
|
||||
- read this `AGENTS.md`;
|
||||
- read the `agent-memory/SKILL.md` skill;
|
||||
- read `.agents/MEMORY.md` for the current project handoff;
|
||||
- read `docs/PHASES.md` to understand the current phase and milestones;
|
||||
- read any domain-specific `RUNBOOK.md` relevant to the task (e.g.
|
||||
`docs/RUNBOOK.md` or `docs/multimedia/RUNBOOK.md`).
|
||||
|
||||
Update `.agents/MEMORY.md` when the task produces significant state, blockers,
|
||||
or decisions. Update the relevant `RUNBOOK.md` when the task reveals reusable
|
||||
operational observations. Update `docs/PHASES.md` when a phase is completed or
|
||||
its plan changes.
|
||||
|
||||
## Skills
|
||||
|
||||
Skills are task-specific instructions and must be read before planning work.
|
||||
|
||||
Before producing a plan, identify all skills applicable to the task and read
|
||||
their `SKILL.md` files.
|
||||
|
||||
Skills are self-contained directories under `.agents/skills/`.
|
||||
|
||||
When a skill is selected, read its `SKILL.md` and consult any relevant
|
||||
documentation under that skill's `references/` directory.
|
||||
|
||||
Skills are task-specific guidance, not a replacement for the mandatory project
|
||||
workflow in this file. The agent must follow `AGENTS.md` even when no skill
|
||||
applies.
|
||||
|
||||
### Skill selection
|
||||
|
||||
- C++ project, Meson build, dependencies, or toolchain issues:
|
||||
`cpp-meson-build/SKILL.md`
|
||||
- Linux screen/audio capture, PipeWire, xdg-desktop-portal, FFmpeg, VAAPI, or
|
||||
codec work:
|
||||
`linux-multimedia-capture/SKILL.md`
|
||||
- RTP/UDP networking, transport, signaling, NAT traversal, or encryption:
|
||||
`rtp-networking/SKILL.md`
|
||||
- Overall screencast application architecture, sender/receiver modes,
|
||||
protocol negotiation, or integration decisions:
|
||||
`screencast-app/SKILL.md`
|
||||
|
||||
Multiple skills may apply. Read all applicable ones.
|
||||
|
||||
For example, implementing sender-side capture + encode will normally require:
|
||||
|
||||
1. `cpp-meson-build/SKILL.md`
|
||||
2. `linux-multimedia-capture/SKILL.md`
|
||||
3. `screencast-app/SKILL.md`
|
||||
|
||||
A networking change will normally require:
|
||||
|
||||
1. `cpp-meson-build/SKILL.md`
|
||||
2. `rtp-networking/SKILL.md`
|
||||
3. `screencast-app/SKILL.md`
|
||||
|
||||
Do not assume that a skill is applicable merely because its subject is mentioned
|
||||
incidentally. Use the task's actual scope to determine applicability.
|
||||
|
||||
## Mandatory development workflow
|
||||
|
||||
All non-trivial implementation tasks follow this workflow:
|
||||
|
||||
### 1. Understand
|
||||
|
||||
Before changing anything:
|
||||
|
||||
- read `AGENTS.md`;
|
||||
- read all applicable skills;
|
||||
- inspect the existing implementation and directory structure;
|
||||
- search the project for related symbols, build rules, and tests;
|
||||
- identify system dependencies (pkg-config names, headers, runtime packages);
|
||||
- identify dependencies and integration points.
|
||||
|
||||
Do not modify files during this phase.
|
||||
|
||||
### Plan against the phase roadmap
|
||||
|
||||
- `docs/PHASES.md` defines the project milestones and the current active phase.
|
||||
- **Do not start implementation that belongs to a later phase** unless the user
|
||||
explicitly asks for it or the current phase is already complete and
|
||||
validated.
|
||||
- When planning work, identify which phase(s) the task touches and include that
|
||||
in the implementation plan.
|
||||
- Update `docs/PHASES.md` when a phase is finished, blocked, or its scope
|
||||
changes; update `.agents/MEMORY.md` at the same time.
|
||||
|
||||
### 2. Plan
|
||||
|
||||
Produce a concise implementation plan containing:
|
||||
|
||||
- understanding of the requirement;
|
||||
- relevant existing project functionality;
|
||||
- files/components likely to change;
|
||||
- proposed implementation;
|
||||
- build/dependency implications;
|
||||
- tests to add or modify;
|
||||
- multimedia/networking/security considerations;
|
||||
- potential risks or unresolved questions.
|
||||
|
||||
### 3. Wait for approval
|
||||
|
||||
**STOP after presenting the plan.**
|
||||
|
||||
Do not create, modify, delete, or rename files until the user explicitly
|
||||
approves the plan.
|
||||
|
||||
Questions and clarification are allowed during this phase.
|
||||
|
||||
### 4. Implement
|
||||
|
||||
After approval:
|
||||
|
||||
- implement the approved plan;
|
||||
- prefer existing project modules and standard library functionality;
|
||||
- keep the change as small and focused as practical;
|
||||
- do not introduce unrelated refactoring;
|
||||
- follow the C++ and project conventions described in this file and in skills.
|
||||
|
||||
If implementation reveals that the approved plan is materially wrong or
|
||||
incomplete, stop and explain the discrepancy rather than silently expanding
|
||||
scope.
|
||||
|
||||
### 5. Validate
|
||||
|
||||
Run appropriate tests and checks.
|
||||
|
||||
At minimum:
|
||||
|
||||
- the project must still configure with Meson (`meson setup build`);
|
||||
- the project must still compile (`ninja -C build`);
|
||||
- unit/integration tests pass (`meson test -C build --print-errorlogs`);
|
||||
- static analysis or formatting checks configured in the project;
|
||||
- manual smoke test of the affected component where automated tests are
|
||||
insufficient.
|
||||
|
||||
For multimedia or networking changes, validate runtime behavior on an actual
|
||||
Linux desktop session (Wayland or X11) when feasible.
|
||||
|
||||
### 6. Review
|
||||
|
||||
Before considering the task complete, review the resulting changes for:
|
||||
|
||||
- correctness;
|
||||
- integration with existing modules;
|
||||
- reuse of existing functionality;
|
||||
- maintainability (small headers, clear ownership, RAII);
|
||||
- security (buffer handling, bounds checks, no secrets committed);
|
||||
- thread safety and async I/O correctness;
|
||||
- regression risk;
|
||||
- build/dependency impact.
|
||||
|
||||
## Verify before assuming
|
||||
|
||||
Never invent or assume the existence of an API.
|
||||
|
||||
Before using a:
|
||||
|
||||
- system library or package;
|
||||
- header file or symbol;
|
||||
- class, function, or macro;
|
||||
- build option or pkg-config name;
|
||||
- external protocol or service;
|
||||
|
||||
verify that it exists in the current environment, project dependencies, or a
|
||||
reliable source (distribution package index, upstream API docs, local header).
|
||||
|
||||
Do not infer existence from naming conventions, training knowledge, or
|
||||
similarly named components. If existence cannot be verified, stop and report the
|
||||
uncertainty.
|
||||
|
||||
In particular, never add a dependency merely because it would be a plausible
|
||||
package name.
|
||||
|
||||
## Prefer framework and standard functionality
|
||||
|
||||
Before implementing custom functionality, search the project and the C++
|
||||
standard library for an existing mechanism.
|
||||
|
||||
Prefer, in order:
|
||||
|
||||
1. existing project functionality that already satisfies the requirement;
|
||||
2. C++ standard library facilities (`std::`, `<format>`, `<chrono>`, etc.);
|
||||
3. widely adopted third-party libraries already used by the project;
|
||||
4. a small project-specific implementation only when the above are insufficient.
|
||||
|
||||
Do not duplicate functionality merely because implementing it locally appears
|
||||
simpler.
|
||||
|
||||
When choosing a custom implementation over an existing mechanism, explain the
|
||||
reason in the plan.
|
||||
|
||||
## Git
|
||||
|
||||
- Keep commits focused and logically coherent.
|
||||
- Prefer one component or one phase per commit where practical.
|
||||
- Do not mix unrelated changes.
|
||||
- Review the staged diff before committing.
|
||||
- Never commit secrets, credentials, local configuration, or generated build
|
||||
artifacts.
|
||||
- Do not create a commit unless the user explicitly asks for one or the task
|
||||
explicitly requires it.
|
||||
- When committing, write concise, descriptive messages in imperative mood.
|
||||
|
||||
## Coding conventions
|
||||
|
||||
### Language baseline
|
||||
|
||||
- C++20 is mandatory. The project must compile with `-std=c++20` and all
|
||||
source must use C++20 idioms.
|
||||
- Prefer standard library facilities over hand-written alternatives.
|
||||
- No C-style constructs when a modern C++ equivalent exists.
|
||||
|
||||
### Modern C++ guidelines
|
||||
|
||||
- **Ownership**: use `std::unique_ptr` and `std::shared_ptr`; never use raw
|
||||
`new`/`delete` or raw owning pointers.
|
||||
- **Optional values**: use `std::optional<T>` instead of sentinel values or
|
||||
out-parameters.
|
||||
- **Fallible operations**: use `std::expected<T,E>` or `std::error_code` for
|
||||
recoverable errors. Avoid exceptions for control flow.
|
||||
- **References and views**: use `std::span<T>` for non-owning contiguous data
|
||||
and `std::string_view` for read-only strings.
|
||||
- **Compile-time computation**: prefer `constexpr`, `consteval`, and
|
||||
`std::integral_constant` where applicable.
|
||||
- **Type safety**: use `enum class`, `std::variant`, and `std::optional`
|
||||
instead of bare integers or bool flags.
|
||||
- **Concurrency**: prefer `std::jthread`, `std::stop_token`, and standard
|
||||
synchronization primitives. Avoid manual thread lifecycle management.
|
||||
- **Containers**: use `std::vector`, `std::array`, `std::deque`, and standard
|
||||
containers; avoid C arrays and raw buffers.
|
||||
- **Formatting and time**: use `<format>` / `std::format` and `<chrono>` instead
|
||||
of `printf`, `sprintf`, or manual time math.
|
||||
- **Algorithms**: prefer `std::ranges` algorithms over hand-rolled loops
|
||||
where clarity improves.
|
||||
- **Lambdas and callable abstractions**: use `std::function` sparingly; prefer
|
||||
function pointers, templates, or `auto` parameters for simple callbacks.
|
||||
- **Concepts and constraints**: use `requires` clauses or `std::concepts` to
|
||||
express interface contracts where beneficial.
|
||||
|
||||
### Style
|
||||
|
||||
- Use `namespace sc` for all project code.
|
||||
- Prefer `snake_case` for functions and variables, `PascalCase` for types,
|
||||
`SCREAMING_SNAKE_CASE` for constants.
|
||||
- Keep headers minimal and free of unnecessary includes; forward-declare where
|
||||
possible.
|
||||
- Format code with the configured formatter (`clang-format` if available).
|
||||
|
||||
### Error handling
|
||||
|
||||
- Use explicit error handling. Prefer `std::expected`, `std::optional`, or
|
||||
`std::error_code` over exceptions for recoverable errors.
|
||||
- Use RAII for resource cleanup; do not leave resources unreleased on error
|
||||
paths.
|
||||
- Avoid `assert` for user-facing error conditions; use real error returns or
|
||||
logging.
|
||||
@@ -0,0 +1,40 @@
|
||||
# screen_cast
|
||||
|
||||
A native Linux peer-to-peer screencast application.
|
||||
|
||||
- **Send** your desktop or a window to another Linux machine.
|
||||
- **Receive** a stream and render it in a window.
|
||||
- Built with **C++20**, **Meson**, **PipeWire**, **FFmpeg**, **RTP/UDP**, and
|
||||
**SDL**.
|
||||
|
||||
> This project is in early development. Only the skeleton exists so far.
|
||||
|
||||
## Quick start
|
||||
|
||||
Requirements:
|
||||
|
||||
- C++20 compiler (GCC 12+, Clang 16+)
|
||||
- Meson + Ninja
|
||||
- (later phases) FFmpeg dev packages, PipeWire dev, SDL2/3 dev
|
||||
|
||||
Build and run tests:
|
||||
|
||||
```sh
|
||||
meson setup build
|
||||
meson compile -C build
|
||||
meson test -C build --print-errorlogs
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
See `docs/ARCHITECTURE.md` for module boundaries and design rules.
|
||||
|
||||
## Roadmap
|
||||
|
||||
Development is split into phases in `docs/PHASES.md`.
|
||||
|
||||
Current phase: **Phase 1 — project skeleton**.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see `LICENSE` (to be added).
|
||||
@@ -0,0 +1,69 @@
|
||||
# Architecture
|
||||
|
||||
`screen_cast` is a native Linux screencast application that sends and receives
|
||||
desktop video over the LAN. It is intentionally split into small, replaceable
|
||||
modules so that capture, codec, network, and rendering concerns can evolve
|
||||
independently.
|
||||
|
||||
## Goals
|
||||
|
||||
- Works on modern Linux desktops (Wayland and X11).
|
||||
- Low-latency peer-to-peer streaming.
|
||||
- CLI-first; optional GUI later.
|
||||
- Software and hardware-accelerated encoding paths.
|
||||
- Minimal, auditable network protocol.
|
||||
|
||||
## Module overview
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌────────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ Capture │────▶│ Encoder │────▶│ Packetize │────▶│ RTP/UDP │ Sender
|
||||
│ (PipeWire) │ │ (FFmpeg) │ │ (RTP) │ │ Transport│
|
||||
└─────────────┘ └────────────┘ └─────────────┘ └──────────┘
|
||||
|
||||
┌──────────┐ ┌─────────────┐ ┌────────────┐ ┌─────────────┐
|
||||
│ RTP/UDP │────▶│ Depacketize │────▶│ Decoder │────▶│ Renderer │ Receiver
|
||||
│Transport │ │ (RTP) │ │ (FFmpeg) │ │ (SDL/GL) │
|
||||
└──────────┘ └─────────────┘ └────────────┘ └─────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ Control / Signaling / Discovery │
|
||||
│ WebSocket/JSON control + mDNS/Avahi discovery │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Module responsibilities
|
||||
|
||||
| Module | Directory | Responsibility |
|
||||
|---|---|---|
|
||||
| Capture | `include/screencast/capture/` | Acquire raw frames from PipeWire/portal. |
|
||||
| Codec | `include/screencast/codec/` | Encode to / decode from H.264 with FFmpeg. |
|
||||
| Network | `include/screencast/network/` | RTP framing, UDP transport, signaling, discovery. |
|
||||
| Render | `include/screencast/render/` | Display decoded frames. |
|
||||
| App | `include/screencast/app/` | CLI parsing, sender/receiver pipelines. |
|
||||
|
||||
## Pipeline rules
|
||||
|
||||
- Each pipeline owns its modules and threads.
|
||||
- Frames are transferred as owned buffers (`std::vector<std::byte>`).
|
||||
- Timestamps start in the capture clock domain and are converted to RTP clock
|
||||
domain once at packetization.
|
||||
- On sender error the transport is drained and stopped.
|
||||
- On receiver loss a PLI is sent over signaling; the sender inserts a keyframe.
|
||||
|
||||
## Namespace
|
||||
|
||||
All project code lives in `namespace sc`.
|
||||
|
||||
## Dependencies (planned)
|
||||
|
||||
| Library | pkg-config | Phase |
|
||||
|---|---|---|
|
||||
| FFmpeg | `libavcodec`, `libavutil`, `libswscale` | 2 |
|
||||
| PipeWire | `libpipewire-0.3` | 3 |
|
||||
| SDL2/3 | `sdl2` / `sdl3` | 5 |
|
||||
| ASIO | bundled or standalone `asio` | 6 |
|
||||
| Avahi | `avahi-client` | 7 |
|
||||
| WebSocket | `websocketpp` or `uWebSockets` | 7 |
|
||||
|
||||
See `docs/PHASES.md` for the phased build plan.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Development Phases
|
||||
|
||||
This file breaks the project into incremental milestones. Each phase produces a
|
||||
working, testable slice of functionality. Do not start a phase until the
|
||||
previous one is validated.
|
||||
|
||||
## Phase 1 — Project Skeleton
|
||||
|
||||
**Goal**: configure, compile, and run tests with no real dependencies.
|
||||
|
||||
- [x] Meson build files (`meson.build`, `meson_options.txt`).
|
||||
- [x] Public module headers with `namespace sc`.
|
||||
- [ ] Unit test that exercises a trivial utility function.
|
||||
- [ ] `README.md` with build instructions.
|
||||
|
||||
**Validation**: `meson setup build && meson compile -C build && meson test -C build`.
|
||||
|
||||
## Phase 2 — Software H.264 Encode / Decode
|
||||
|
||||
**Goal**: encode raw pixel buffers to H.264 and decode them back, purely with
|
||||
FFmpeg software paths.
|
||||
|
||||
- Add `libavcodec`, `libavutil`, `libswscale` dependencies.
|
||||
- Implement `EncoderFactory::create()` and `Encoder::encode()`.
|
||||
- Implement `DecoderFactory::create()` and `Decoder::decode()`.
|
||||
- Round-trip test: synthetic RGB frames → H.264 → decoded RGB.
|
||||
|
||||
**Validation**: unit test produces visually/structurally correct round-trip
|
||||
frames.
|
||||
|
||||
## Phase 3 — PipeWire Screen Capture
|
||||
|
||||
**Goal**: capture the desktop and feed frames into the encoder.
|
||||
|
||||
- Add `libpipewire-0.3` dependency.
|
||||
- Implement `CaptureFactory::create()` using the xdg-desktop-portal.
|
||||
- Wire `CaptureSession::next_frame()` → `Encoder::encode()` in a local smoke
|
||||
test that just writes a few encoded frames to disk.
|
||||
|
||||
**Validation**: manual run on a real Linux desktop session produces a valid
|
||||
H.264 bitstream.
|
||||
|
||||
## Phase 4 — RTP Framing
|
||||
|
||||
**Goal**: packetize NAL units into RTP and depacketize them.
|
||||
|
||||
- Implement `RtpHeader` and `RtpPacket` serialize/parse.
|
||||
- Add H.264 NAL splitting and FU-A fragmentation.
|
||||
- Unit test for serialization, fragmentation, and reassembly.
|
||||
|
||||
**Validation**: unit tests cover single-NAL and fragmented packet paths.
|
||||
|
||||
## Phase 5 — Local UDP Sender → Receiver Loopback
|
||||
|
||||
**Goal**: send RTP packets over UDP and render the result locally.
|
||||
|
||||
- Implement `RtpTransport` with ASIO or raw UDP sockets.
|
||||
- Wire sender pipeline: capture → encode → RTP → localhost UDP.
|
||||
- Wire receiver pipeline: localhost UDP → RTP → decode → renderer.
|
||||
- Add SDL2/3 dependency and a minimal `Renderer`.
|
||||
|
||||
**Validation**: `screencast --send` and `screencast --receive` on the same
|
||||
machine show the captured desktop in a window.
|
||||
|
||||
## Phase 6 — LAN Signaling and Discovery
|
||||
|
||||
**Goal**: two peers on the same LAN can find each other and negotiate a
|
||||
session.
|
||||
|
||||
- Implement mDNS/DNS-SD discovery with Avahi.
|
||||
- Implement JSON WebSocket signaling.
|
||||
- Extend CLI with `--peer-address` or `--discover`.
|
||||
|
||||
**Validation**: two machines on the same LAN connect without hard-coded IP
|
||||
addresses.
|
||||
|
||||
## Phase 7 — Resilience and Polish
|
||||
|
||||
**Goal**: loss recovery, hardware acceleration, and packaging.
|
||||
|
||||
- NACK / PLI feedback loop.
|
||||
- Jitter buffer on the receiver.
|
||||
- VAAPI/NVENC hardware encode probes and fallback.
|
||||
- Optional GUI target behind `meson -Dgui=true`.
|
||||
- `.desktop` file, icon, packaging notes.
|
||||
|
||||
**Validation**: sustained streaming under packet loss; hardware accel smoke
|
||||
where available.
|
||||
|
||||
## Current phase
|
||||
|
||||
Phase 1 — skeleton and tooling.
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
namespace sc {
|
||||
|
||||
struct SendCommand {
|
||||
std::string_view target = "monitor"; // monitor, window, region
|
||||
std::string_view peer_address; // optional
|
||||
int bitrate_kbps = 4000;
|
||||
};
|
||||
|
||||
struct ReceiveCommand {
|
||||
std::string_view peer_address; // optional
|
||||
int local_rtp_port = 5004;
|
||||
};
|
||||
|
||||
using Command = std::variant<SendCommand, ReceiveCommand>;
|
||||
|
||||
// Parse command line arguments. Prints usage and returns std::nullopt on error.
|
||||
std::optional<Command> parse_cli ( int argc, const char *argv[] );
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "screencast/capture/capture.h"
|
||||
#include "screencast/codec/decoder.h"
|
||||
#include "screencast/codec/encoder.h"
|
||||
#include "screencast/network/discovery.h"
|
||||
#include "screencast/network/signaling.h"
|
||||
#include "screencast/network/transport.h"
|
||||
#include "screencast/render/renderer.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace sc {
|
||||
|
||||
struct SenderPipelineConfig {
|
||||
CaptureTarget capture_target;
|
||||
EncoderConfig encoder;
|
||||
Endpoint local_rtp_endpoint;
|
||||
std::optional<Endpoint> signaling_server;
|
||||
};
|
||||
|
||||
struct ReceiverPipelineConfig {
|
||||
DecoderConfig decoder;
|
||||
Endpoint local_rtp_endpoint;
|
||||
std::optional<Endpoint> peer_signaling;
|
||||
RendererConfig renderer;
|
||||
};
|
||||
|
||||
// Sender pipeline: capture → encode → packetize → RTP/UDP.
|
||||
class SenderPipeline {
|
||||
public:
|
||||
explicit SenderPipeline ( SenderPipelineConfig config );
|
||||
~SenderPipeline ();
|
||||
|
||||
bool start ();
|
||||
void stop ();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
// Receiver pipeline: RTP/UDP → depacketize → decode → render.
|
||||
class ReceiverPipeline {
|
||||
public:
|
||||
explicit ReceiverPipeline ( ReceiverPipelineConfig config );
|
||||
~ReceiverPipeline ();
|
||||
|
||||
bool start ();
|
||||
void stop ();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace sc {
|
||||
|
||||
// Opaque resource owned by the capture implementation.
|
||||
struct CapturedFrame {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
uint64_t timestamp_ns = 0; // capture clock, monotonic
|
||||
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;
|
||||
};
|
||||
struct CaptureTargetRegion {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
using CaptureTarget = std::variant<
|
||||
CaptureTargetWholeScreen,
|
||||
CaptureTargetWindow,
|
||||
CaptureTargetRegion>;
|
||||
|
||||
class CaptureSession {
|
||||
public:
|
||||
virtual ~CaptureSession() = default;
|
||||
|
||||
// Blocking call to acquire one frame. Returns std::nullopt on graceful stop.
|
||||
virtual std::optional<CapturedFrame> next_frame() = 0;
|
||||
|
||||
// Request the session to stop. May be called from another thread.
|
||||
virtual void stop() = 0;
|
||||
};
|
||||
|
||||
// Factory for the PipeWire / xdg-desktop-portal capture backend.
|
||||
class CaptureFactory {
|
||||
public:
|
||||
static std::unique_ptr<CaptureSession> create(CaptureTarget target);
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "screencast/codec/encoder.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace sc {
|
||||
|
||||
struct DecodedFrame {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
uint64_t capture_timestamp_ns = 0;
|
||||
std::vector<std::byte> rgba_pixels;
|
||||
};
|
||||
|
||||
struct DecoderConfig {
|
||||
std::string_view codec_name = "h264";
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
class Decoder {
|
||||
public:
|
||||
virtual ~Decoder () = default;
|
||||
|
||||
// Feed one encoded frame. Returns decoded frames when available.
|
||||
virtual std::vector<DecodedFrame> decode ( const EncodedFrame &frame ) = 0;
|
||||
};
|
||||
|
||||
class DecoderFactory {
|
||||
public:
|
||||
static std::unique_ptr<Decoder> create ( const DecoderConfig &config );
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "screencast/capture/capture.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace sc {
|
||||
|
||||
struct EncodedFrame {
|
||||
uint64_t capture_timestamp_ns = 0;
|
||||
uint32_t rtp_timestamp = 0;
|
||||
bool is_keyframe = false;
|
||||
std::vector<std::byte> data; // Annex-B or AVCC depending on config
|
||||
};
|
||||
|
||||
struct EncoderConfig {
|
||||
std::string_view codec_name = "h264";
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int frame_rate_num = 30;
|
||||
int frame_rate_den = 1;
|
||||
int bitrate_kbps = 4000;
|
||||
bool hardware_accel = false;
|
||||
};
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
virtual ~Encoder () = default;
|
||||
|
||||
// Encode one captured frame. Returns empty if the encoder emits no packet
|
||||
// for this frame.
|
||||
virtual std::vector<EncodedFrame> encode ( const CapturedFrame &frame ) = 0;
|
||||
|
||||
// Force the next output to be a keyframe.
|
||||
virtual void request_keyframe () = 0;
|
||||
};
|
||||
|
||||
class EncoderFactory {
|
||||
public:
|
||||
static std::unique_ptr<Encoder> create ( const EncoderConfig &config );
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace sc {
|
||||
|
||||
struct DiscoveredPeer {
|
||||
std::string service_name;
|
||||
std::string host;
|
||||
uint16_t signaling_port = 0;
|
||||
};
|
||||
|
||||
class DiscoveryService {
|
||||
public:
|
||||
using PeerCallback = std::function<void ( const DiscoveredPeer & )>;
|
||||
|
||||
virtual ~DiscoveryService () = default;
|
||||
|
||||
// Announce this peer on the LAN.
|
||||
virtual bool announce ( const std::string &service_name, uint16_t signaling_port ) = 0;
|
||||
|
||||
// Browse for peers; callback is invoked for each newly found service.
|
||||
virtual bool browse ( PeerCallback on_peer ) = 0;
|
||||
|
||||
virtual void stop () = 0;
|
||||
};
|
||||
|
||||
class DiscoveryFactory {
|
||||
public:
|
||||
static std::unique_ptr<DiscoveryService> create_avahi ();
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace sc {
|
||||
|
||||
// Minimal RTP header (RFC 3550) without extensions.
|
||||
struct RtpHeader {
|
||||
uint8_t version = 2;
|
||||
bool padding = false;
|
||||
bool extension = false;
|
||||
uint8_t csrc_count = 0;
|
||||
bool marker = false;
|
||||
uint7_t payload_type = 96; // dynamic
|
||||
uint16_t sequence_number = 0;
|
||||
uint32_t timestamp = 0;
|
||||
uint32_t ssrc = 0;
|
||||
|
||||
bool serialize ( std::span<std::byte, 12> out ) const noexcept;
|
||||
static std::optional<RtpHeader> parse ( std::span<const std::byte, 12> in ) noexcept;
|
||||
};
|
||||
|
||||
struct RtpPacket {
|
||||
RtpHeader header;
|
||||
std::vector<std::byte> payload;
|
||||
|
||||
std::vector<std::byte> serialize () const;
|
||||
static std::optional<RtpPacket> parse ( std::span<const std::byte> in ) noexcept;
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "screencast/network/transport.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace sc {
|
||||
|
||||
// JSON-based control messages exchanged before or during a session.
|
||||
struct SessionOffer {
|
||||
std::string session_id;
|
||||
std::string codec_name;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int frame_rate_num = 30;
|
||||
int frame_rate_den = 1;
|
||||
Endpoint rtp_endpoint;
|
||||
};
|
||||
|
||||
struct SessionAnswer {
|
||||
std::string session_id;
|
||||
Endpoint rtp_endpoint;
|
||||
};
|
||||
|
||||
using SignalingMessage = std::variant<SessionOffer, SessionAnswer>;
|
||||
|
||||
class SignalingChannel {
|
||||
public:
|
||||
using MessageCallback = std::function<void ( const SignalingMessage & )>;
|
||||
|
||||
virtual ~SignalingChannel () = default;
|
||||
|
||||
virtual bool connect ( const Endpoint &server ) = 0;
|
||||
virtual void send ( const SignalingMessage &message ) = 0;
|
||||
virtual void on_message ( MessageCallback callback ) = 0;
|
||||
virtual void disconnect () = 0;
|
||||
};
|
||||
|
||||
class SignalingFactory {
|
||||
public:
|
||||
static std::unique_ptr<SignalingChannel> create_websocket_client ();
|
||||
static std::unique_ptr<SignalingChannel> create_websocket_server ( uint16_t port );
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "screencast/network/rtp_packet.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace sc {
|
||||
|
||||
struct Endpoint {
|
||||
std::string address;
|
||||
uint16_t port = 0;
|
||||
};
|
||||
|
||||
// UDP transport for RTP packets. Owned by the sender or receiver pipeline.
|
||||
class RtpTransport {
|
||||
public:
|
||||
using ReceiveCallback = std::function<void ( RtpPacket )>;
|
||||
|
||||
virtual ~RtpTransport () = default;
|
||||
|
||||
// Bind locally and start the receive loop. Callback is invoked on the
|
||||
// transport's thread.
|
||||
virtual bool start ( const Endpoint &local_endpoint, ReceiveCallback on_receive ) = 0;
|
||||
|
||||
// Send a packet to the configured peer.
|
||||
virtual bool send ( const RtpPacket &packet ) = 0;
|
||||
|
||||
// Set the peer endpoint dynamically (e.g. after signaling).
|
||||
virtual void set_peer ( const Endpoint &peer ) = 0;
|
||||
|
||||
// Stop the transport and close sockets.
|
||||
virtual void stop () = 0;
|
||||
};
|
||||
|
||||
class RtpTransportFactory {
|
||||
public:
|
||||
static std::unique_ptr<RtpTransport> create ();
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "screencast/codec/decoder.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace sc {
|
||||
|
||||
struct RendererConfig {
|
||||
std::string_view window_title = "screencast receiver";
|
||||
int initial_width = 1280;
|
||||
int initial_height = 720;
|
||||
};
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
virtual ~Renderer () = default;
|
||||
|
||||
// Present one decoded frame. Returns false if the window was closed.
|
||||
virtual bool present ( const DecodedFrame &frame ) = 0;
|
||||
|
||||
// Pump events (window close, resize). Non-blocking.
|
||||
virtual bool poll_events () = 0;
|
||||
|
||||
// Destroy the window and release GPU resources.
|
||||
virtual void shutdown () = 0;
|
||||
};
|
||||
|
||||
class RendererFactory {
|
||||
public:
|
||||
static std::unique_ptr<Renderer> create ( const RendererConfig &config );
|
||||
};
|
||||
|
||||
} // namespace sc
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
project('screen_cast', 'cpp',
|
||||
version : '0.1.0',
|
||||
default_options : [
|
||||
'cpp_std=c++20',
|
||||
'warning_level=3',
|
||||
'werror=false',
|
||||
'buildtype=release',
|
||||
])
|
||||
|
||||
# Public and private include directories
|
||||
inc = include_directories('include')
|
||||
|
||||
# Build the core static library first; dependencies will be added later as
|
||||
# phases require them.
|
||||
subdir('src')
|
||||
|
||||
# Tests
|
||||
enable_tests = get_option('tests')
|
||||
if enable_tests
|
||||
subdir('tests')
|
||||
endif
|
||||
|
||||
# Summary for the user
|
||||
summary({
|
||||
'tests': enable_tests,
|
||||
}, section: 'Build options')
|
||||
@@ -0,0 +1,5 @@
|
||||
option('tests', type : 'boolean', value : true,
|
||||
description : 'Build unit and integration tests')
|
||||
|
||||
option('gui', type : 'boolean', value : false,
|
||||
description : 'Build the optional GUI target (deferred to a later phase)')
|
||||
@@ -0,0 +1,2 @@
|
||||
# Empty stub: implementation files will be added phase by phase.
|
||||
# For now the project configures successfully with no targets.
|
||||
@@ -0,0 +1 @@
|
||||
# Empty stub: tests will be added as modules land.
|
||||
Reference in New Issue
Block a user