6e0273f4b4
В прод деплоен gx/cuframes-composer:0.11b-step1 — C++ ядро
работает через ABI shim, старые C-callers (grid_record.c, control.c,
frigate_mqtt.c) использует те же cfc_composer_* функции.
Что в этом коммите:
- src/cpp/composer_c_api.cpp: extern "C" обёртки над cfc::Composer
методами. Полный набор: _create/_destroy/_compose/_add_overlay/
_find_overlay/_set_layout/_current_layout/_add_pool_source/
_set_motion_mode/_get_motion_mode/_motion_pulse/_get_health.
- src/cpp/layouts_c_api.cpp: extern "C" обёртки над template_loader
для cfc_layout_find/_all/_load_file/_reload/_loaded_path/_to_pixels.
- cpp/template_loader: global registry (current_templates / set_*
/ load_into_current) — единый источник истины. Composer и C ABI
shim читают один и тот же mutex-защищённый vector<LayoutTemplate>.
Hot-reload через ZMQ cfc_layout_load_file подхватывается composer'ом
на следующем кадре без рестарта.
- cpp/composer: pick_best_fit, set_layout, maybe_relayout читают
current_templates() вместо локального snapshot.
- cpp/composer: backward-compat overlay list (add_overlay/find_overlay)
+ manual cells support (для C API без motion-mode).
- cpp/composer compose_frame: после Layout.render() рендерит overlays
(CLI text/icon/border + Frigate detbox) поверх.
- Удалены: src/composer.c (заменён composer_c_api.cpp + composer.cpp),
src/layouts.c (заменён layouts_c_api.cpp + template_loader.cpp).
- Оставлено как есть: src/overlay.c (PNG/text/border/detbox CLI overlays
— реализация не меняется, доступ через cfc_overlay_*).
- src/CMakeLists.txt: COMPOSER_SOURCES_C минус composer.c, layouts.c,
COMPOSER_SOURCES_CPP плюс composer_c_api.cpp, layouts_c_api.cpp.
Production smoke (R9-88.23):
[cfc/loader] /opt/templates.json: loaded 7 templates
[cfc/composer] templates loaded: 7 (path='/opt/templates.json')
[cfc/composer] pool+ cam-parking prio=100 / cam-gate_lpr prio=90 / ...
[cfc/composer] motion_mode=1 ttl=45000ms pool=4
[cfc/composer] grow → template='tpl_1' active=1
PASS.
Refs: #195 (Phase 11b C++ refactor).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
148 lines
5.0 KiB
C++
148 lines
5.0 KiB
C++
/* Composer — оркестратор Phase 11b.
|
||
*
|
||
* Owns:
|
||
* - SourcePool (cuframes-источники + motion state)
|
||
* - vector<LayoutTemplate> (loaded from JSON или builtins)
|
||
* - Layout (текущее состояние cells)
|
||
* - OutputSurface (CudaBuffer для NV12 output)
|
||
*
|
||
* Compose loop (по кадру):
|
||
* 1. select_template_and_active() → (LayoutTemplate*, vector<PoolEntry*>)
|
||
* по правилам: motion_mode? motion-based best-fit : idle top-1 single
|
||
* 2. hysteresis: рост сразу, уменьшение — wait shrink_hysteresis_ms
|
||
* 3. если sig != committed → layout_.apply(template, active, W, H)
|
||
* 4. compose_clear() → output буфер чёрный
|
||
* 5. layout_.render(stream, NV12Ref)
|
||
*
|
||
* Public API экспортируется через composer_c_api.cpp с extern "C" для
|
||
* совместимости с control.c, grid_record.c, frigate_mqtt.c.
|
||
*
|
||
* Лицензия: LGPL-2.1+
|
||
*/
|
||
|
||
#ifndef CUFRAMES_COMPOSER_CPP_COMPOSER_HPP
|
||
#define CUFRAMES_COMPOSER_CPP_COMPOSER_HPP
|
||
|
||
#include "../overlay.h" /* C API — backward compat для CLI overlays */
|
||
#include "cuda_raii.hpp"
|
||
#include "layout.hpp"
|
||
#include "source_pool.hpp"
|
||
#include "template.hpp"
|
||
#include "types.hpp"
|
||
|
||
#include <cuda_runtime.h>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
namespace cfc {
|
||
|
||
struct ComposerConfig {
|
||
int width = 1920;
|
||
int height = 1080;
|
||
int cuda_device = 0;
|
||
int bg_y = 16, bg_u = 128, bg_v = 128;
|
||
|
||
/* Motion-mode параметры. */
|
||
int motion_ttl_ms = 45000;
|
||
int shrink_hysteresis_ms = 3000;
|
||
|
||
/* Templates JSON path (empty → built-in). */
|
||
std::string templates_path;
|
||
};
|
||
|
||
class Composer {
|
||
public:
|
||
explicit Composer(const ComposerConfig& cfg);
|
||
~Composer();
|
||
|
||
Composer(const Composer&) = delete;
|
||
Composer& operator=(const Composer&) = delete;
|
||
|
||
bool ok() const noexcept { return output_.ok(); }
|
||
|
||
SourcePool& pool() noexcept { return pool_; }
|
||
const SourcePool& pool() const noexcept { return pool_; }
|
||
|
||
/* Motion mode + relayout policy. */
|
||
void set_motion_mode(bool on, int ttl_ms = 0);
|
||
bool motion_mode() const noexcept { return motion_mode_; }
|
||
|
||
/* Загрузить templates из JSON. Возвращает количество, либо <0. */
|
||
int load_templates(const std::string& path);
|
||
|
||
/* Перейти на named template (только если motion_mode == false). */
|
||
bool set_layout(const std::string& name);
|
||
|
||
const std::string& current_layout_name() const noexcept {
|
||
return layout_.name();
|
||
}
|
||
int templates_count() const noexcept {
|
||
return static_cast<int>(templates_.size());
|
||
}
|
||
const std::vector<LayoutTemplate>& templates() const noexcept {
|
||
return templates_;
|
||
}
|
||
|
||
/* Overlays — backward compat для grid_record.c CLI (--text/--icon/--border)
|
||
* и Frigate detection_boxes. Рисуются на выходе compose_frame ПОСЛЕ Layout.
|
||
* Composer takes ownership — destroy()'ит на ~Composer(). */
|
||
int add_overlay(cfc_overlay_t* ov);
|
||
cfc_overlay_t* find_overlay(const std::string& id) const;
|
||
|
||
/* Health отчёт для C ABI shim. */
|
||
struct Health {
|
||
int total = 0;
|
||
int active = 0;
|
||
int stale = 0;
|
||
int dead = 0;
|
||
};
|
||
Health get_health() const;
|
||
|
||
/* Manual cells — для C API без motion-mode (grid_record --cell без --motion-mode).
|
||
* Каждый вход {source_key, rect} рендерится CameraCell без template'а. */
|
||
void set_manual_cells(const std::vector<std::pair<std::string, Rect>>& cells);
|
||
|
||
/* Один кадр: relayout (если нужно) + clear + render.
|
||
* Возвращает NV12Ref на output (ptr действителен до следующего compose). */
|
||
NV12Ref compose_frame();
|
||
|
||
private:
|
||
/* Selection + hysteresis. */
|
||
const LayoutTemplate* pick_best_fit(int need) const;
|
||
std::vector<PoolEntry*> collect_active() const;
|
||
void maybe_relayout();
|
||
static std::string build_signature(const std::string& tpl_name,
|
||
const std::vector<PoolEntry*>& active);
|
||
|
||
ComposerConfig cfg_;
|
||
SourcePool pool_;
|
||
std::vector<LayoutTemplate> templates_;
|
||
Layout layout_;
|
||
|
||
/* Output NV12 буфер (VMM, zero-copy для NVENC). */
|
||
CudaBuffer output_;
|
||
int pitch_y_ = 0;
|
||
int pitch_uv_ = 0;
|
||
|
||
cudaStream_t stream_ = nullptr; /* default = 0 */
|
||
|
||
bool motion_mode_ = false;
|
||
std::int64_t committed_at_ms_ = 0;
|
||
std::int64_t pending_first_seen_ms_ = 0;
|
||
std::string committed_signature_;
|
||
std::string pending_signature_;
|
||
|
||
/* Backward-compat overlay list (CLI overlays + detbox). */
|
||
std::vector<cfc_overlay_t*> overlays_;
|
||
|
||
/* Manual cells — alternative режим без motion-mode (grid_record --cell). */
|
||
std::vector<std::pair<std::string, Rect>> manual_cells_;
|
||
bool manual_applied_ = false;
|
||
};
|
||
|
||
} // namespace cfc
|
||
|
||
#endif /* CUFRAMES_COMPOSER_CPP_COMPOSER_HPP */
|