feat: scaffold project framework with engine, node skeleton, and passthrough node

- CMake build system with vcpkg dependency management
- libdmf-node: Node interface, port definitions, WebSocket control
  server, node runner with MXL lifecycle management
- libdmf-engine: Graph model (nodes/edges CRUD, serialization),
  FlowManager (UUID + V210 NMOS flow definitions), ProcessManager
  (fork/exec node processes), ApiServer (REST API for graph control)
- Passthrough node: reads V210 grains from MXL, copies, writes to MXL
- Unit tests for Graph and FlowManager (6 cases, 21 assertions)
- MXL SDK as git submodule (symlinked from extern/mxl)
This commit is contained in:
Johanness
2026-05-26 01:32:50 +03:00
parent ea0eaf8d48
commit 5b5ffa1308
30 changed files with 1547 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
add_executable(dmf-node-passthrough
src/main.cpp
src/passthrough_node.cpp
)
target_link_libraries(dmf-node-passthrough PRIVATE dmf-node)
+6
View File
@@ -0,0 +1,6 @@
#include <dmf-node/node_runner.hpp>
#include "passthrough_node.hpp"
int main(int argc, char* argv[]) {
return dmf_node::NodeRunner::run<dmf_node::PassthroughNode>(argc, argv);
}
@@ -0,0 +1,94 @@
#include "passthrough_node.hpp"
#include <mxl/flow.h>
#include <mxl/mxl.h>
#include <spdlog/spdlog.h>
#include <cstring>
namespace dmf_node {
void PassthroughNode::on_add_writer(const std::string& port_id, mxlFlowWriter writer) {
if (port_id == "video_out") {
writer_ = writer;
spdlog::info("Passthrough: writer added on video_out");
}
}
void PassthroughNode::on_add_reader(const std::string& port_id, mxlFlowReader reader) {
if (port_id == "video_in") {
reader_ = reader;
spdlog::info("Passthrough: reader added on video_in");
}
}
void PassthroughNode::on_remove_writer(const std::string& port_id) {
if (port_id == "video_out") {
writer_.reset();
spdlog::info("Passthrough: writer removed from video_out");
}
}
void PassthroughNode::on_remove_reader(const std::string& port_id) {
if (port_id == "video_in") {
reader_.reset();
spdlog::info("Passthrough: reader removed from video_in");
}
}
void PassthroughNode::process() {
if (!reader_ || !writer_) {
return;
}
mxlGrainInfo grain_info{};
uint8_t* payload = nullptr;
auto status = mxlFlowReaderGetGrain(*reader_, read_index_, 100000000ULL, &grain_info, &payload);
if (status != MXL_STATUS_OK) {
if (status == MXL_ERR_TIMEOUT) {
return;
}
if (status == MXL_ERR_OUT_OF_RANGE_TOO_LATE) {
mxlFlowRuntimeInfo runtime{};
mxlFlowReaderGetRuntimeInfo(*reader_, &runtime);
read_index_ = runtime.headIndex;
return;
}
return;
}
mxlGrainInfo out_grain{};
uint8_t* out_payload = nullptr;
status = mxlFlowWriterOpenGrain(*writer_, write_index_, &out_grain, &out_payload);
if (status != MXL_STATUS_OK) {
spdlog::warn("Passthrough: failed to open output grain at index {}: {}", write_index_, static_cast<int>(status));
read_index_++;
return;
}
auto copy_size = std::min(grain_info.grainSize, out_grain.grainSize);
std::memcpy(out_payload, payload, copy_size);
out_grain.validSlices = grain_info.validSlices;
out_grain.flags = grain_info.flags;
mxlFlowWriterCommitGrain(*writer_, &out_grain);
read_index_++;
write_index_++;
grains_processed_++;
}
nlohmann::json PassthroughNode::status() const {
return {
{"type", "passthrough"},
{"grains_processed", grains_processed_},
{"read_index", read_index_},
{"write_index", write_index_},
{"has_reader", reader_.has_value()},
{"has_writer", writer_.has_value()},
};
}
} // namespace dmf_node
@@ -0,0 +1,45 @@
#pragma once
#include <dmf-node/node.hpp>
#include <mxl/flow.h>
#include <unordered_map>
#include <optional>
namespace dmf_node {
class PassthroughNode : public Node {
public:
PassthroughNode() = default;
std::string type() const override { return "passthrough"; }
std::vector<PortDef> input_ports() const override {
return {{"video_in", PortDirection::Input, MediaType::VideoV210}};
}
std::vector<PortDef> output_ports() const override {
return {{"video_out", PortDirection::Output, MediaType::VideoV210}};
}
void configure(const nlohmann::json& /*params*/) override {}
void on_add_writer(const std::string& port_id, mxlFlowWriter writer) override;
void on_add_reader(const std::string& port_id, mxlFlowReader reader) override;
void on_remove_writer(const std::string& port_id) override;
void on_remove_reader(const std::string& port_id) override;
void process() override;
nlohmann::json status() const override;
private:
std::optional<mxlFlowReader> reader_;
std::optional<mxlFlowWriter> writer_;
mxlInstance mxl_instance_ = nullptr;
uint64_t read_index_ = 0;
uint64_t write_index_ = 0;
uint64_t grains_processed_ = 0;
};
} // namespace dmf_node