Initial commit: testpattern → fakesink pipeline over MXL shared memory

- NodeBase, Signal, FlowDef, V210 shared headers
- testpattern node: SMPTE 75% color bars writer at 25fps
- fakesink node: non-blocking MXL reader with per-second stats
- studio-manager: FlowGraph data model, graph-driven fork/exec launcher
- mxl pinned as submodule at 0ae1dc5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
JohannesItten
2026-06-23 13:35:01 +03:00
commit ca682eaf0a
17 changed files with 1269 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
#pragma once
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <nlohmann/json.hpp>
#include <mxl/mxl.h>
#include "Signal.hpp"
namespace dmf {
// Base class for all DMF node binaries.
//
// Handles the boilerplate every node needs:
// - NODE_CONFIG env var → parsed JSON config
// - MXL_DOMAIN env var → domain path
// - mxlCreateInstance / mxlDestroyInstance lifecycle
// - Signal handler installation
// - [node_id]-prefixed logging
//
// Derived class implements only run(), which receives a valid MXL instance
// and runs until dmf::g_running is false (set by SIGTERM/SIGINT).
class NodeBase {
public:
virtual ~NodeBase() = default;
// Entry point for main(). Returns 0 on success, 1 on error.
int execute() {
install_signal_handlers();
const char* config_env = std::getenv("NODE_CONFIG");
if (!config_env) {
fprintf(stderr, "[node] NODE_CONFIG not set\n");
return 1;
}
cfg_ = nlohmann::json::parse(config_env, nullptr, /*allow_exceptions=*/false);
if (cfg_.is_discarded()) {
fprintf(stderr, "[node] NODE_CONFIG is not valid JSON\n");
return 1;
}
node_id_ = cfg_.value("node_id", std::string("node"));
const char* domain_env = std::getenv("MXL_DOMAIN");
#ifdef __APPLE__
domain_ = domain_env ? domain_env : "/tmp/dmf-studio";
#else
domain_ = domain_env ? domain_env : "/dev/shm/dmf-studio";
#endif
log("domain=%s", domain_.c_str());
inst_ = mxlCreateInstance(domain_.c_str(), nullptr);
if (!inst_) {
log("mxlCreateInstance failed at %s", domain_.c_str());
return 1;
}
run();
mxlDestroyInstance(inst_);
inst_ = nullptr;
return 0;
}
protected:
// Implement the node's processing loop here.
// Create MXL writers/readers, loop while dmf::g_running, release them before returning.
virtual void run() = 0;
const nlohmann::json& config() const { return cfg_; }
mxlInstance instance() const { return inst_; }
const std::string& node_id() const { return node_id_; }
const std::string& domain() const { return domain_; }
// Printf-style log with automatic "[node_id] " prefix and trailing newline.
#if defined(__GNUC__) || defined(__clang__)
__attribute__((format(printf, 2, 3)))
#endif
void log(const char* fmt, ...) const {
fprintf(stderr, "[%s] ", node_id_.c_str());
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fputc('\n', stderr);
}
private:
nlohmann::json cfg_;
mxlInstance inst_{nullptr};
std::string node_id_{"node"};
std::string domain_;
};
} // namespace dmf