Files
cuframes-composer/src/cpp/composer_c_api.cpp
T
gx 6e0273f4b4 Phase 11b F: extern "C" ABI shim + production deploy
В прод деплоен 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>
2026-06-04 08:57:30 +01:00

198 lines
6.5 KiB
C++

/* composer_c_api — extern "C" ABI shim для C++ Composer (Phase 11b).
*
* Существующие callers (control.c, frigate_mqtt.c, examples/grid_record.c)
* продолжают использовать prototype cfc_composer_* без изменений. Здесь
* каждый из них транслируется в вызов соответствующего метода cfc::Composer.
*
* Opaque handle cfc_composer_t = cfc::Composer (через reinterpret_cast).
*
* Лицензия: LGPL-2.1+
*/
#include "../../include/cuframes_composer/composer.h"
#include "../../include/cuframes_composer/cpp/composer.hpp"
#include <cstdio>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
namespace {
inline cfc::Composer* as_cpp(cfc_composer_t* h)
{
return reinterpret_cast<cfc::Composer*>(h);
}
inline cfc_composer_t* as_c(cfc::Composer* c)
{
return reinterpret_cast<cfc_composer_t*>(c);
}
} // namespace
extern "C" {
int cfc_composer_create(const cfc_composer_config_t* cfg, cfc_composer_t** out)
{
if (!cfg || !out) return -1;
if (cfg->width <= 0 || cfg->height <= 0) return -1;
cfc::ComposerConfig cpp_cfg;
cpp_cfg.width = cfg->width;
cpp_cfg.height = cfg->height;
cpp_cfg.cuda_device = cfg->cuda_device;
if (cfg->bg_y) cpp_cfg.bg_y = cfg->bg_y;
if (cfg->bg_u) cpp_cfg.bg_u = cfg->bg_u;
if (cfg->bg_v) cpp_cfg.bg_v = cfg->bg_v;
auto* comp = new (std::nothrow) cfc::Composer(cpp_cfg);
if (!comp || !comp->ok()) {
delete comp;
return -1;
}
/* Если caller передал cells через --cell → запоминаем как manual cells.
* Apply отложен до compose_frame (тогда pool уже наполнен через
* add_pool_source). */
if (cfg->cells && cfg->num_cells > 0) {
std::vector<std::pair<std::string, cfc::Rect>> manual;
for (int i = 0; i < cfg->num_cells; i++) {
const auto& c = cfg->cells[i];
if (!c.source_key) continue;
cfc::Rect r;
r.x = c.x; r.y = c.y; r.w = c.w; r.h = c.h;
manual.emplace_back(std::string(c.source_key), r);
}
comp->set_manual_cells(manual);
/* Также добавляем источник в pool автоматически — иначе lookup
* не найдёт его. Priority=0, frigate=none, zones=[]. */
cfc::SourcePool::SubscribeOpts opts;
if (cfg->consumer_prefix && *cfg->consumer_prefix)
opts.consumer_prefix = cfg->consumer_prefix;
if (cfg->reconnect_min_ms) opts.reconnect_min_ms = cfg->reconnect_min_ms;
if (cfg->reconnect_max_ms) opts.reconnect_max_ms = cfg->reconnect_max_ms;
if (cfg->stale_threshold_ms) opts.stale_threshold_ms = cfg->stale_threshold_ms;
if (cfg->dead_threshold_ms) opts.dead_threshold_ms = cfg->dead_threshold_ms;
for (const auto& kv : manual) {
comp->pool().add(kv.first, "", 0, {}, opts);
}
}
std::fprintf(stderr, "[cfc/composer] C++ ABI shim, %dx%d, %d manual cells\n",
cfg->width, cfg->height, cfg->num_cells);
*out = as_c(comp);
return 0;
}
int cfc_composer_compose(cfc_composer_t* h,
CUdeviceptr* out_y_ptr,
int* out_pitch_y,
int* out_width,
int* out_height)
{
if (!h) return -1;
cfc::NV12Ref ref = as_cpp(h)->compose_frame();
if (out_y_ptr) *out_y_ptr = ref.y_ptr;
if (out_pitch_y) *out_pitch_y = ref.pitch_y;
if (out_width) *out_width = ref.frame_w;
if (out_height) *out_height = ref.frame_h;
return 0;
}
int cfc_composer_add_overlay(cfc_composer_t* h, cfc_overlay_t* ov)
{
if (!h) return -1;
return as_cpp(h)->add_overlay(ov);
}
cfc_overlay_t* cfc_composer_find_overlay(cfc_composer_t* h, const char* id)
{
if (!h || !id) return nullptr;
return as_cpp(h)->find_overlay(id);
}
int cfc_composer_set_layout(cfc_composer_t* h, const char* layout_name)
{
if (!h || !layout_name) return -1;
return as_cpp(h)->set_layout(layout_name) ? 0 : -1;
}
const char* cfc_composer_current_layout(cfc_composer_t* h)
{
if (!h) return nullptr;
const std::string& n = as_cpp(h)->current_layout_name();
return n.empty() ? nullptr : n.c_str();
}
int cfc_composer_add_pool_source(cfc_composer_t* h,
const char* cuframes_key,
const char* frigate_camera,
int priority,
const char* required_zones)
{
if (!h || !cuframes_key) return -1;
std::vector<std::string> zones;
if (required_zones && *required_zones) {
std::string cur;
for (const char* p = required_zones; *p; p++) {
if (*p == ':') { if (!cur.empty()) zones.push_back(cur); cur.clear(); }
else cur.push_back(*p);
}
if (!cur.empty()) zones.push_back(cur);
}
cfc::SourcePool::SubscribeOpts opts;
int idx = as_cpp(h)->pool().add(cuframes_key,
frigate_camera ? frigate_camera : "",
priority, zones, opts);
std::fprintf(stderr, "[cfc/composer] pool+ '%s' (frigate=%s prio=%d zones=%zu) → idx=%d\n",
cuframes_key, frigate_camera ? frigate_camera : "-",
priority, zones.size(), idx);
return idx >= 0 ? 0 : -1;
}
int cfc_composer_set_motion_mode(cfc_composer_t* h, int on, int ttl_ms)
{
if (!h) return -1;
as_cpp(h)->set_motion_mode(on != 0, ttl_ms);
return 0;
}
int cfc_composer_get_motion_mode(cfc_composer_t* h)
{
return h ? (as_cpp(h)->motion_mode() ? 1 : 0) : 0;
}
int cfc_composer_motion_pulse(cfc_composer_t* h,
const char* frigate_camera,
const char* const* current_zones,
int n_zones)
{
if (!h || !frigate_camera) return -1;
std::vector<std::string> zones;
for (int i = 0; i < n_zones; i++) {
if (current_zones[i]) zones.emplace_back(current_zones[i]);
}
as_cpp(h)->pool().motion_pulse(frigate_camera, zones);
return 0;
}
int cfc_composer_get_health(cfc_composer_t* h, cfc_composer_health_t* out)
{
if (!h || !out) return -1;
auto hh = as_cpp(h)->get_health();
out->total = hh.total;
out->active = hh.active;
out->stale = hh.stale;
out->dead = hh.dead;
return 0;
}
int cfc_composer_destroy(cfc_composer_t* h)
{
if (h) delete as_cpp(h);
return 0;
}
} // extern "C"