/* grid_record_cpp — Phase 11b ООП-гипотеза. * * Минимальный smoke: проверяет что C++ модель Cell/Layout/Decoration/Composer * компилируется, линкуется с C-частями, и реально рисует кадры через те же * CUDA-kernels (zero-copy: единый NV12 буфер не копируется между cells). * * Делает N композиций → dump последнего NV12 кадра в файл → exit. * Без NVENC: цель гипотезы — не encode, а доказать что ООП-pipeline работает. * * Использование: * grid_record_cpp --out /tmp/last.nv12 --frames 50 \ * --templates /opt/templates.json \ * --source cam-parking,frigate=parking_overview,priority=100 \ * --source cam-back_yard,frigate=back_yard,priority=70 \ * --motion-mode */ #include "../include/cuframes_composer/cpp/composer.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace { volatile sig_atomic_t g_stop = 0; void on_sig(int) { g_stop = 1; } struct SourceSpec { std::string key; std::string frigate; int priority = 0; std::vector zones; }; std::vector split_colon(const std::string& s) { std::vector out; std::string cur; for (char c : s) { if (c == ':') { if (!cur.empty()) out.push_back(cur); cur.clear(); } else cur.push_back(c); } if (!cur.empty()) out.push_back(cur); return out; } SourceSpec parse_source(const std::string& arg) { SourceSpec s; std::vector parts; std::string cur; for (char c : arg) { if (c == ',') { parts.push_back(cur); cur.clear(); } else cur.push_back(c); } if (!cur.empty()) parts.push_back(cur); if (parts.empty()) return s; s.key = parts[0]; for (std::size_t i = 1; i < parts.size(); i++) { auto& p = parts[i]; if (p.rfind("frigate=", 0) == 0) s.frigate = p.substr(8); else if (p.rfind("priority=", 0) == 0) s.priority = std::atoi(p.c_str() + 9); else if (p.rfind("zones=", 0) == 0) s.zones = split_colon(p.substr(6)); } return s; } } // namespace int main(int argc, char** argv) { std::string out_path; std::string templates_path; int width = 1920, height = 1080; int frames_to_compose = 25; bool motion_mode = false; int motion_ttl = 45000; std::vector sources; static struct option opts[] = { {"out", required_argument, 0, 'o'}, {"frames", required_argument, 0, 'n'}, {"width", required_argument, 0, 'W'}, {"height", required_argument, 0, 'H'}, {"source", required_argument, 0, 'S'}, {"motion-mode",no_argument, 0, 'm'}, {"motion-ttl", required_argument, 0, 'k'}, {"templates", required_argument, 0, 'z'}, {0, 0, 0, 0}, }; int c; while ((c = getopt_long(argc, argv, "o:n:W:H:S:mk:z:", opts, nullptr)) != -1) { switch (c) { case 'o': out_path = optarg; break; case 'n': frames_to_compose = std::atoi(optarg); break; case 'W': width = std::atoi(optarg); break; case 'H': height = std::atoi(optarg); break; case 'S': sources.push_back(parse_source(optarg)); break; case 'm': motion_mode = true; break; case 'k': motion_ttl = std::atoi(optarg); break; case 'z': templates_path = optarg; break; default: return 1; } } if (out_path.empty()) { std::fprintf(stderr, "Usage: %s --out FILE --source ... [--motion-mode]\n", argv[0]); return 1; } signal(SIGINT, on_sig); signal(SIGTERM, on_sig); cuInit(0); CUdevice dev; cuDeviceGet(&dev, 0); CUcontext ctx; cuDevicePrimaryCtxRetain(&ctx, dev); cuCtxPushCurrent(ctx); cfc::ComposerConfig ccfg; ccfg.width = width; ccfg.height = height; ccfg.templates_path = templates_path; ccfg.motion_ttl_ms = motion_ttl; cfc::Composer composer(ccfg); if (!composer.ok()) { std::fprintf(stderr, "[smoke] composer init failed\n"); return 1; } cfc::SourcePool::SubscribeOpts opts_sub; for (auto& s : sources) { composer.pool().add(s.key, s.frigate, s.priority, s.zones, opts_sub); } if (motion_mode) composer.set_motion_mode(true, motion_ttl); std::fprintf(stderr, "[smoke] composer %dx%d templates=%d sources=%zu motion=%d\n", width, height, composer.templates_count(), sources.size(), motion_mode ? 1 : 0); /* Несколько композиций — даём sources подключиться. */ cfc::NV12Ref last{}; for (int i = 0; i < frames_to_compose && !g_stop; i++) { last = composer.compose_frame(); cudaStreamSynchronize(0); std::this_thread::sleep_for(std::chrono::milliseconds(40)); } /* Dump последнего кадра в файл. */ std::size_t y_size = static_cast(last.pitch_y) * height; std::size_t uv_size = static_cast(last.pitch_uv) * (height / 2); std::vector host(y_size + uv_size); cuMemcpyDtoH(host.data(), last.y_ptr, y_size); cuMemcpyDtoH(host.data() + y_size, last.uv_ptr, uv_size); FILE* f = std::fopen(out_path.c_str(), "wb"); if (!f) { std::fprintf(stderr, "[smoke] open '%s' failed\n", out_path.c_str()); return 1; } std::fwrite(host.data(), 1, host.size(), f); std::fclose(f); std::fprintf(stderr, "[smoke] wrote %zu bytes (Y=%zu UV=%zu) to %s\n", host.size(), y_size, uv_size, out_path.c_str()); std::fprintf(stderr, "[smoke] current template: '%s'\n", composer.current_layout_name().c_str()); cuCtxPopCurrent(nullptr); cuDevicePrimaryCtxRelease(dev); return 0; }