st-2110-20 pipeline API MTL node in
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
#pragma once
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <mtl/mtl_api.h>
|
||||
#include <mtl/st20_api.h>
|
||||
#include <mtl/st_pipeline_api.h>
|
||||
}
|
||||
|
||||
#include <mxl/flow.h>
|
||||
|
||||
#include "FlowDef.hpp"
|
||||
#include "NodeBase.hpp"
|
||||
|
||||
namespace dmf {
|
||||
|
||||
struct ST2110ReceiverConfig {
|
||||
std::string flow_id;
|
||||
std::string ifname;
|
||||
std::string local_ip;
|
||||
std::string source_ip;
|
||||
std::string mcast_ip;
|
||||
uint16_t udp_port = 0;
|
||||
uint8_t payload_type = 96;
|
||||
int width = 1920;
|
||||
int height = 1080;
|
||||
int fps_num = 25;
|
||||
int fps_den = 1;
|
||||
int framebuff_cnt = 3;
|
||||
};
|
||||
|
||||
inline void st2110_set_ip(uint8_t dst[MTL_IP_ADDR_LEN], const std::string& ip) {
|
||||
if (inet_pton(AF_INET, ip.c_str(), dst) != 1) {
|
||||
throw std::runtime_error("invalid IP address: " + ip);
|
||||
}
|
||||
}
|
||||
|
||||
inline uint16_t st2110_checked_u16(const nlohmann::json& j, const char* key) {
|
||||
const int value = j.at(key).get<int>();
|
||||
if (value < 0 || value > 65535) {
|
||||
throw std::runtime_error(std::string(key) + " out of uint16 range");
|
||||
}
|
||||
return static_cast<uint16_t>(value);
|
||||
}
|
||||
|
||||
inline uint8_t st2110_checked_u8(const nlohmann::json& j, const char* key, int fallback) {
|
||||
const int value = j.value(key, fallback);
|
||||
if (value < 0 || value > 255) {
|
||||
throw std::runtime_error(std::string(key) + " out of uint8 range");
|
||||
}
|
||||
return static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
inline enum st_fps st2110_to_st_fps(int fps_num, int fps_den) {
|
||||
if (fps_den == 1) {
|
||||
switch (fps_num) {
|
||||
case 24: return ST_FPS_P24;
|
||||
case 25: return ST_FPS_P25;
|
||||
case 30: return ST_FPS_P30;
|
||||
case 50: return ST_FPS_P50;
|
||||
case 60: return ST_FPS_P60;
|
||||
case 100: return ST_FPS_P100;
|
||||
case 120: return ST_FPS_P120;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (fps_num == 24000 && fps_den == 1001) return ST_FPS_P23_98;
|
||||
if (fps_num == 30000 && fps_den == 1001) return ST_FPS_P29_97;
|
||||
if (fps_num == 60000 && fps_den == 1001) return ST_FPS_P59_94;
|
||||
if (fps_num == 120000 && fps_den == 1001) return ST_FPS_P119_88;
|
||||
throw std::runtime_error("unsupported ST 2110 frame rate");
|
||||
}
|
||||
|
||||
inline ST2110ReceiverConfig parse_st2110_receiver_config(const nlohmann::json& cfg) {
|
||||
if (!cfg.contains("video_flow_id")) {
|
||||
throw std::runtime_error("no video output connected");
|
||||
}
|
||||
|
||||
ST2110ReceiverConfig out;
|
||||
out.flow_id = cfg.at("video_flow_id").at("id").get<std::string>();
|
||||
out.ifname = cfg.at("interface").get<std::string>();
|
||||
out.local_ip = cfg.at("local_ip").get<std::string>();
|
||||
out.source_ip = cfg.at("source_ip").get<std::string>();
|
||||
out.mcast_ip = cfg.at("mcast_ip").get<std::string>();
|
||||
out.udp_port = st2110_checked_u16(cfg, "udp_port");
|
||||
out.payload_type = st2110_checked_u8(cfg, "payload_type", 96);
|
||||
out.width = cfg.value("width", out.width);
|
||||
out.height = cfg.value("height", out.height);
|
||||
out.fps_num = cfg.value("fps_num", out.fps_num);
|
||||
out.fps_den = cfg.value("fps_den", out.fps_den);
|
||||
out.framebuff_cnt = cfg.value("framebuff_cnt", out.framebuff_cnt);
|
||||
|
||||
if (out.width <= 0 || out.height <= 0) {
|
||||
throw std::runtime_error("width and height must be positive");
|
||||
}
|
||||
if (out.width % 6 != 0) {
|
||||
throw std::runtime_error("width must be divisible by 6 for v210 output");
|
||||
}
|
||||
if (out.fps_num <= 0 || out.fps_den <= 0) {
|
||||
throw std::runtime_error("fps_num and fps_den must be positive");
|
||||
}
|
||||
if (out.framebuff_cnt < 2 || out.framebuff_cnt > ST20_FB_MAX_COUNT) {
|
||||
throw std::runtime_error("framebuff_cnt must be in [2, ST20_FB_MAX_COUNT]");
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
class MTLContext {
|
||||
public:
|
||||
explicit MTLContext(const ST2110ReceiverConfig& cfg) {
|
||||
mtl_init_params params{};
|
||||
params.num_ports = 1;
|
||||
|
||||
port_name_ = "kernel:" + cfg.ifname;
|
||||
std::snprintf(params.port[MTL_PORT_P], sizeof(params.port[MTL_PORT_P]), "%s",
|
||||
port_name_.c_str());
|
||||
|
||||
params.pmd[MTL_PORT_P] = MTL_PMD_KERNEL_SOCKET;
|
||||
params.net_proto[MTL_PORT_P] = MTL_PROTO_STATIC;
|
||||
params.rx_queues_cnt[MTL_PORT_P] = 1;
|
||||
params.tx_queues_cnt[MTL_PORT_P] = 0;
|
||||
params.log_level = MTL_LOG_LEVEL_INFO;
|
||||
params.flags = MTL_FLAG_DEV_AUTO_START_STOP;
|
||||
st2110_set_ip(params.sip_addr[MTL_PORT_P], cfg.local_ip);
|
||||
|
||||
handle_ = mtl_init(¶ms);
|
||||
if (!handle_) {
|
||||
throw std::runtime_error("mtl_init failed");
|
||||
}
|
||||
}
|
||||
|
||||
~MTLContext() {
|
||||
if (handle_) {
|
||||
mtl_uninit(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
MTLContext(const MTLContext&) = delete;
|
||||
MTLContext& operator=(const MTLContext&) = delete;
|
||||
|
||||
mtl_handle get() const { return handle_; }
|
||||
const std::string& port_name() const { return port_name_; }
|
||||
|
||||
private:
|
||||
mtl_handle handle_{nullptr};
|
||||
std::string port_name_;
|
||||
};
|
||||
|
||||
class ST20RxSession {
|
||||
public:
|
||||
ST20RxSession(mtl_handle mt, const std::string& port_name,
|
||||
const ST2110ReceiverConfig& cfg) {
|
||||
st20p_rx_ops ops{};
|
||||
ops.name = "dmf-st2110in-video";
|
||||
ops.port.num_port = 1;
|
||||
ops.port.udp_port[MTL_SESSION_PORT_P] = cfg.udp_port;
|
||||
ops.port.payload_type = cfg.payload_type;
|
||||
std::snprintf(ops.port.port[MTL_SESSION_PORT_P],
|
||||
sizeof(ops.port.port[MTL_SESSION_PORT_P]), "%s", port_name.c_str());
|
||||
st2110_set_ip(ops.port.ip_addr[MTL_SESSION_PORT_P], cfg.mcast_ip);
|
||||
st2110_set_ip(ops.port.mcast_sip_addr[MTL_SESSION_PORT_P], cfg.source_ip);
|
||||
|
||||
ops.width = static_cast<uint32_t>(cfg.width);
|
||||
ops.height = static_cast<uint32_t>(cfg.height);
|
||||
ops.fps = st2110_to_st_fps(cfg.fps_num, cfg.fps_den);
|
||||
ops.interlaced = false;
|
||||
ops.transport_fmt = ST20_FMT_YUV_422_8BIT;
|
||||
ops.output_fmt = ST_FRAME_FMT_UYVY;
|
||||
ops.device = ST_PLUGIN_DEVICE_AUTO;
|
||||
ops.framebuff_cnt = static_cast<uint16_t>(cfg.framebuff_cnt);
|
||||
ops.flags = ST20P_RX_FLAG_BLOCK_GET;
|
||||
|
||||
handle_ = st20p_rx_create(mt, &ops);
|
||||
if (!handle_) {
|
||||
throw std::runtime_error("st20p_rx_create failed");
|
||||
}
|
||||
st20p_rx_set_block_timeout(handle_, 100'000'000);
|
||||
}
|
||||
|
||||
~ST20RxSession() {
|
||||
if (handle_) {
|
||||
st20p_rx_wake_block(handle_);
|
||||
st20p_rx_free(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
ST20RxSession(const ST20RxSession&) = delete;
|
||||
ST20RxSession& operator=(const ST20RxSession&) = delete;
|
||||
|
||||
st20p_rx_handle get() const { return handle_; }
|
||||
|
||||
private:
|
||||
st20p_rx_handle handle_{nullptr};
|
||||
};
|
||||
|
||||
class MXLVideoWriter {
|
||||
public:
|
||||
MXLVideoWriter(mxlInstance instance, const ST2110ReceiverConfig& cfg,
|
||||
const std::string& node_id)
|
||||
: instance_(instance) {
|
||||
bool created = false;
|
||||
const std::string flow_def =
|
||||
make_video_flow_def(cfg.flow_id, node_id, cfg.width, cfg.height, cfg.fps_num,
|
||||
cfg.fps_den);
|
||||
const mxlStatus st =
|
||||
mxlCreateFlowWriter(instance_, flow_def.c_str(), "", &writer_, &config_, &created);
|
||||
if (st != MXL_STATUS_OK) {
|
||||
throw std::runtime_error(std::string("mxlCreateFlowWriter failed: ") +
|
||||
mxl_status_str(st));
|
||||
}
|
||||
}
|
||||
|
||||
~MXLVideoWriter() {
|
||||
if (writer_) {
|
||||
mxlReleaseFlowWriter(instance_, writer_);
|
||||
}
|
||||
}
|
||||
|
||||
MXLVideoWriter(const MXLVideoWriter&) = delete;
|
||||
MXLVideoWriter& operator=(const MXLVideoWriter&) = delete;
|
||||
|
||||
mxlFlowWriter get() const { return writer_; }
|
||||
const mxlFlowConfigInfo& config() const { return config_; }
|
||||
|
||||
private:
|
||||
mxlInstance instance_{nullptr};
|
||||
mxlFlowWriter writer_{nullptr};
|
||||
mxlFlowConfigInfo config_{};
|
||||
};
|
||||
|
||||
} // namespace dmf
|
||||
+1
-1
@@ -119,7 +119,7 @@ inline void fill_white(uint8_t* buf, int width, int height, uint32_t stride)
|
||||
fill_solid(buf, width, height, stride, {940, 512, 512});
|
||||
}
|
||||
|
||||
inline void UYVYtoV210(uint8_t* src_buf, uint8_t* dst_buf, int width, int height, uint32_t src_stride, uint32_t dst_stride)
|
||||
inline void UYVYtoV210(const uint8_t* src_buf, uint8_t* dst_buf, int width, int height, uint32_t src_stride, uint32_t dst_stride)
|
||||
{
|
||||
const uint8_t* src = src_buf;
|
||||
uint8_t* dst = dst_buf;
|
||||
|
||||
Reference in New Issue
Block a user