#pragma once #include #include #include namespace dmf { // Generates a minimal but valid NMOS IS-04 flow definition JSON string // for a video/v210 flow. MXL uses this to set up the ring buffer geometry. inline std::string make_video_flow_def( const std::string& flow_id, const std::string& label, int width, int height, int fps_num, int fps_den = 1) { using json = nlohmann::json; return json{ {"id", flow_id}, {"format", "urn:x-nmos:format:video"}, {"label", label}, {"description", label + " MXL Video Flow"}, {"media_type", "video/v210"}, {"parents", json::array()}, {"grain_rate", {{"numerator", fps_num}, {"denominator", fps_den}}}, {"frame_width", width}, {"frame_height", height}, {"interlace_mode", "progressive"}, {"colorspace", "BT709"}, {"tags", { {"urn:x-nmos:tag:grouphint/v1.0", json::array({label + ":Video"})} }}, {"components", json::array({ {{"name","Y"}, {"width",width}, {"height",height}, {"bit_depth",10}}, {{"name","Cb"}, {"width",width/2}, {"height",height}, {"bit_depth",10}}, {{"name","Cr"}, {"width",width/2}, {"height",height}, {"bit_depth",10}} })} }.dump(); } // Generates a minimal but valid NMOS IS-04 flow definition JSON string // for a raw PCM audio flow. grain_rate_num/den sets the grain delivery rate // (default 25/1 = one grain per video frame). MXL computes grain size as // sample_rate / grain_rate samples per grain. inline std::string make_audio_flow_def( const std::string& flow_id, const std::string& label, int sample_rate, int channels, int bit_depth = 32, int grain_rate_num = 25, int grain_rate_den = 1) { using json = nlohmann::json; static const char* ch_labels[] = {"L","R","C","LFE","Ls","Rs","Lss","Rss"}; json ch_arr = json::array(); for (int i = 0; i < channels; ++i) ch_arr.push_back({{"label", i < 8 ? ch_labels[i] : ("Ch" + std::to_string(i + 1))}}); return json{ {"id", flow_id}, {"format", "urn:x-nmos:format:audio"}, {"label", label}, {"description", label + " MXL Audio Flow"}, {"media_type", bit_depth == 32 ? "audio/float32" : "audio/L" + std::to_string(bit_depth)}, {"parents", json::array()}, {"grain_rate", {{"numerator", grain_rate_num}, {"denominator", grain_rate_den}}}, {"sample_rate", {{"numerator", sample_rate}, {"denominator", 1}}}, {"channels", ch_arr}, // NMOS IS-04 metadata (ignored by MXL) {"channel_count", channels}, // MXL uses this for grain buffer geometry {"bit_depth", bit_depth}, // must be 32 or 64 {"tags", { {"urn:x-nmos:tag:grouphint/v1.0", json::array({label + ":Audio"})} }}, }.dump(); } // Actual video format as written into the MXL flow_def.json by a source node. // Sink nodes use this to read width/height/fps from MXL instead of trusting config values. struct VideoFlowInfo { int width = 1920; int height = 1080; int fps_num = 25; int fps_den = 1; }; inline VideoFlowInfo read_video_flow_info(const std::string& domain, const std::string& flow_id) { VideoFlowInfo info; const std::string path = domain + "/" + flow_id + ".mxl-flow/flow_def.json"; std::ifstream f(path); if (!f.is_open()) return info; const auto j = nlohmann::json::parse(f, nullptr, /*allow_exceptions=*/false); if (j.is_discarded()) return info; info.width = j.value("frame_width", 1920); info.height = j.value("frame_height", 1080); if (j.contains("grain_rate")) { info.fps_num = j["grain_rate"].value("numerator", 25); info.fps_den = j["grain_rate"].value("denominator", 1); } return info; } // Actual audio format as written into the MXL flow_def.json by a source node. struct AudioFlowInfo { int sample_rate = 48000; int channels = 2; int samples_per_grain = 1920; // sample_rate * grain_rate_den / grain_rate_num }; inline AudioFlowInfo read_audio_flow_info(const std::string& domain, const std::string& flow_id) { AudioFlowInfo info; const std::string path = domain + "/" + flow_id + ".mxl-flow/flow_def.json"; std::ifstream f(path); if (!f.is_open()) return info; const auto j = nlohmann::json::parse(f, nullptr, /*allow_exceptions=*/false); if (j.is_discarded()) return info; if (j.contains("sample_rate")) info.sample_rate = j["sample_rate"].value("numerator", 48000); info.channels = j.value("channel_count", 2); if (j.contains("grain_rate") && info.sample_rate > 0) { const int gr_num = j["grain_rate"].value("numerator", 25); const int gr_den = j["grain_rate"].value("denominator", 1); if (gr_num > 0) info.samples_per_grain = info.sample_rate * gr_den / gr_num; } return info; } } // namespace dmf