feat(capture): give CaptureFactory a proper error channel
Replace the nullable unique_ptr returned by CaptureFactory::create() with CaptureResult<std::unique_ptr<CaptureSession>> using the new CaptureError/CaptureResult pattern, mirroring codec/error.h. The stub now reports 'not implemented yet' as an error instead of returning nullptr. Update handoff memory with the review outcome and forward-looking notes for phases 3, 5, and 7.
This commit is contained in:
+39
-11
@@ -1,19 +1,31 @@
|
|||||||
# Project Memory — screen_cast
|
# Project Memory — screen_cast
|
||||||
|
|
||||||
Last updated: initial scaffold.
|
Last updated: Phase 2 codec review fixes applied.
|
||||||
|
|
||||||
## Project state
|
## Project state
|
||||||
|
|
||||||
- Phase 2 follow-up review items addressed: encoder rate control is now
|
- Phase 2 codec implementation reviewed with valgrind; all confirmed defects
|
||||||
bitrate-only (removed CRF), encoder/decoder cache their SwsContext, configs
|
fixed on top of the capture-stub commit:
|
||||||
own their strings, bitstream helper is internal to the encoder with a NAL
|
- decoder leaked every packet payload (`av_malloc` + direct `packet->data`
|
||||||
length guard, FFmpeg open errors are reported, and the round-trip test
|
assignment bypassed the packet's owning `AVBufferRef`); payloads are now
|
||||||
verifies Annex-B prefix and keyframes.
|
allocated with `av_new_packet`.
|
||||||
- `.clang-format` added at repo root and clang-format enforcement added to
|
- decoder extradata lacked `AV_INPUT_BUFFER_PADDING_SIZE`; FFmpeg's
|
||||||
`AGENTS.md` and `cpp-meson-build/SKILL.md`.
|
extradata parser over-read the buffer (valgrind invalid reads). Now
|
||||||
- Phase 3 capture now has a linkable stub (`src/capture/pipewire_capture.cpp`)
|
allocated padded and zeroed.
|
||||||
so the API can be consumed without an unresolved symbol.
|
- oversized encoded frames are rejected before the int cast.
|
||||||
- Remaining implementation: capture, transport, rendering, and CLI/pipeline glue.
|
- EAGAIN-retry loops in encoder/decoder now handle unexpected EOF instead
|
||||||
|
of retrying forever.
|
||||||
|
- fixed "RTP packet" → "AVPacket" error message in the encoder.
|
||||||
|
- `CaptureFactory::create()` now returns
|
||||||
|
`CaptureResult<std::unique_ptr<CaptureSession>>` (variant with
|
||||||
|
`CaptureError`) instead of a nullable unique_ptr; the stub reports
|
||||||
|
"PipeWire capture is not implemented yet" as an error. New pattern lives in
|
||||||
|
`include/screencast/capture/error.h`, mirroring `codec/error.h`.
|
||||||
|
- Validation: `meson test` 2/2 OK; valgrind on `test_codec_roundtrip` is now
|
||||||
|
clean (0 definite losses, 0 invalid reads); clang-format clean. See
|
||||||
|
`docs/RUNBOOK.md` for the reusable checks.
|
||||||
|
- Phase 3 capture stub remains in place; remaining implementation: capture,
|
||||||
|
transport, rendering, and CLI/pipeline glue.
|
||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
@@ -28,6 +40,8 @@ Last updated: initial scaffold.
|
|||||||
- Discovery: mDNS/Avahi.
|
- Discovery: mDNS/Avahi.
|
||||||
- Rendering: SDL2 or SDL3 + OpenGL.
|
- Rendering: SDL2 or SDL3 + OpenGL.
|
||||||
- Namespace: `sc`.
|
- Namespace: `sc`.
|
||||||
|
- Module error results use per-module `std::variant<T, XError>` types
|
||||||
|
(`CodecResult`, `CaptureResult`) since C++20 has no `std::expected`.
|
||||||
|
|
||||||
## Active blockers
|
## Active blockers
|
||||||
|
|
||||||
@@ -38,3 +52,17 @@ None.
|
|||||||
- GUI framework (Qt6 vs. none / CLI only) — deferred to later phase.
|
- GUI framework (Qt6 vs. none / CLI only) — deferred to later phase.
|
||||||
- Hardware acceleration strategy (VAAPI / Vulkan Video / NVENC) — evaluate after
|
- Hardware acceleration strategy (VAAPI / Vulkan Video / NVENC) — evaluate after
|
||||||
software encode path works.
|
software encode path works.
|
||||||
|
|
||||||
|
## Forward-looking review notes (for later phases)
|
||||||
|
|
||||||
|
- Encoder rejects non-packed strides in `make_input_frame`; PipeWire/portal
|
||||||
|
frames usually have alignment-padded strides — Phase 3 must pass the real
|
||||||
|
stride through to swscale instead of rejecting it.
|
||||||
|
- `AV_CODEC_FLAG_GLOBAL_HEADER` suppresses in-band SPS/PPS; a receiver cannot
|
||||||
|
join mid-stream or recover after PLI without parameter sets. Phase 5 must
|
||||||
|
prepend SPS/PPS to keyframes or negotiate them in signaling.
|
||||||
|
- Encoder sets no VBV (`maxrate`/`buffer_size`) — ABR only; add for smoother
|
||||||
|
UDP streaming in Phase 7.
|
||||||
|
- No negative-path tests yet (bad config, bad stride, undersized buffer).
|
||||||
|
- `to_annex_b_h264` sniffs AVCC vs Annex-B by content; if an AVCC-emitting
|
||||||
|
encoder is ever added, prefer an explicit config flag over the heuristic.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "screencast/capture/error.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -52,7 +54,7 @@ class CaptureSession {
|
|||||||
// 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 CaptureResult<std::unique_ptr<CaptureSession>> create(CaptureTarget target);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sc
|
} // namespace sc
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace sc {
|
||||||
|
|
||||||
|
struct CaptureError {
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
// C++20 does not provide std::expected. Use a variant-based result type so
|
||||||
|
// fallible capture operations do not rely on exceptions.
|
||||||
|
template <typename T> using CaptureResult = std::variant<T, CaptureError>;
|
||||||
|
|
||||||
|
template <typename T> constexpr bool is_capture_error(const CaptureResult<T>& result) noexcept {
|
||||||
|
return std::holds_alternative<CaptureError>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> T& capture_value(CaptureResult<T>& result) {
|
||||||
|
return std::get<T>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> const T& capture_value(const CaptureResult<T>& result) {
|
||||||
|
return std::get<T>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> CaptureError& capture_error(CaptureResult<T>& result) {
|
||||||
|
return std::get<CaptureError>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> const CaptureError& capture_error(const CaptureResult<T>& result) {
|
||||||
|
return std::get<CaptureError>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sc
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
namespace sc {
|
namespace sc {
|
||||||
|
|
||||||
std::unique_ptr<CaptureSession> CaptureFactory::create(CaptureTarget /*target*/) {
|
CaptureResult<std::unique_ptr<CaptureSession>> CaptureFactory::create(CaptureTarget /*target*/) {
|
||||||
// Phase 3 placeholder. The PipeWire / xdg-desktop-portal capture backend
|
// Phase 3 placeholder. The PipeWire / xdg-desktop-portal capture backend
|
||||||
// will be implemented here once the codec round-trip is solidified.
|
// will be implemented here once the portal integration lands.
|
||||||
return nullptr;
|
return CaptureError{"PipeWire capture is not implemented yet"};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sc
|
} // namespace sc
|
||||||
|
|||||||
Reference in New Issue
Block a user