858fe61b56
User: "PTZ снова не переключает сетки". Причина: при motion-mode set_layout
игнорировался. Теперь: применяется + замораживает motion-mode на
manual_override_duration_ms_ (60s default). По истечении — auto-возврат
в motion-mode.
В Composer добавлено:
- manual_override_until_ms_ (моноклоk монотонное время)
- manual_override_duration_ms_ (default 60s)
- set_layout: применяет template, ставит override timestamp
- maybe_relayout: пока now < override → пропускаем (sustain manual layout),
после → лог "expired, возврат в motion-mode" + force relayout
ONVIF server.py одновременно обновлён под актуальные template имена:
- PTZ_PRESETS: tpl_1 / tpl_4 / tpl_9 / tpl_16 (вместо single/quad/...)
- ContinuousMove zoom-in → tpl_1, zoom-out → tpl_16,
pan/tilt → cycle через эти 4
Production smoke:
GotoPreset tpl_4 → composer log "manual override 'tpl_4' до +60000ms" PASS.
Refs: #195.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
153 lines
5.3 KiB
C++
153 lines
5.3 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_;
|
||
|
||
/* Manual override (PTZ через set_layout): пока now < manual_override_until_ms_
|
||
* motion-mode "заморожен", композитор держит зафиксированный layout. */
|
||
std::int64_t manual_override_until_ms_ = 0;
|
||
int manual_override_duration_ms_ = 60000;
|
||
|
||
/* 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 */
|