first commit

This commit is contained in:
陈赣
2026-06-03 12:43:14 +08:00
commit ba76cfae28
608 changed files with 120791 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
#include "vp_analysis_board.h"
#include "vp_version.h"
#include "../vp_pipe_checker.h"
namespace vp_utils {
vp_analysis_board::vp_analysis_board(std::vector<std::shared_ptr<vp_nodes::vp_node>> src_nodes_in_pipe):
src_nodes_in_pipe(src_nodes_in_pipe) {
init();
}
vp_analysis_board::~vp_analysis_board() {
// set alive to false and wait threads exits
alive = false;
if (display_th.joinable()) {
display_th.join();
}
if (rtmp_th.joinable()) {
rtmp_th.join();
}
}
void vp_analysis_board::init() {
src_nodes_on_screen.clear();
des_nodes_on_screen.clear();
// check pipe
vp_pipe_checker pipe_checker;
pipe_checker(src_nodes_in_pipe);
// layers number and max nodes number of all layers
pipe_width = pipe_checker.pipe_width();
pipe_height = pipe_checker.pipe_height();
// calculate the w and h of canvas
canvas_width = pipe_width * node_width + (pipe_width - 1) * node_gap_horizontal + 2 * canvas_gap_horizontal;
canvas_height = pipe_height * node_height + (pipe_height - 1) * node_gap_vertical + 2 * canvas_gap_vertical;
// create canvas Mat and initialize it with white
bg_canvas.create(canvas_height, canvas_width, CV_8UC3);
bg_canvas = cv::Scalar(255, 255, 255);
// map recursively
map_nodes(src_nodes_on_screen, 1);
// render static parts starting with 1st layer
render_layer(src_nodes_on_screen, bg_canvas);
// save to local by default
save(board_title + ".png");
}
void vp_analysis_board::reload(std::vector<std::shared_ptr<vp_nodes::vp_node>> new_src_nodes_in_pipe) {
std::lock_guard<std::mutex> guard(reload_lock);
if (new_src_nodes_in_pipe.size() != 0) {
this->src_nodes_in_pipe = new_src_nodes_in_pipe;
}
init();
}
void vp_analysis_board::save(std::string path) {
cv::imwrite(path, bg_canvas);
}
void vp_analysis_board::display(int interval, bool block) {
assert(interval > 0);
if (displaying) {
return;
}
auto display_func = [&, interval](){
while (alive) {
auto loop_start = std::chrono::system_clock::now();
{
std::lock_guard<std::mutex> guard(reload_lock); // in case it reloading
// deep copy the static background
cv::Mat mat_to_display = bg_canvas.clone();
// render dynamic parts starting with 1 st layer
render_layer(src_nodes_on_screen, mat_to_display, false);
cv::imshow(board_title, mat_to_display);
}
// calculate the time need wait for
auto loop_cost = std::chrono::system_clock::now() - loop_start;
auto wait_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::seconds(interval) - loop_cost);
cv::waitKey(wait_time.count());
}};
displaying = true;
display_th = std::thread(display_func);
if (block) {
display_th.join();
}
}
void vp_analysis_board::push_rtmp(std::string rtmp, int bitrate) {
if (displaying) {
return;
}
auto fps = 10;
auto rtmp_url = vp_utils::string_format(gst_template, bitrate, rtmp.c_str());
// 10 fps by default
assert(rtmp_writer.open(rtmp_url, cv::CAP_GSTREAMER, fps, {bg_canvas.cols, bg_canvas.rows}));
auto display_func = [&, fps](){
while (alive) {
auto loop_start = std::chrono::system_clock::now();
{
std::lock_guard<std::mutex> guard(reload_lock); // in case it reloading
// deep copy the static background
cv::Mat mat_to_display = bg_canvas.clone();
// render dynamic parts starting with 1 st layer
render_layer(src_nodes_on_screen, mat_to_display, false);
rtmp_writer.write(mat_to_display);
}
// calculate the time need wait for
auto loop_cost = std::chrono::system_clock::now() - loop_start;
auto wait_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::milliseconds(1000 / fps) - loop_cost);
std::this_thread::sleep_for(wait_time);
}};
displaying = true;
rtmp_th = std::thread(display_func);
}
void vp_analysis_board::render_layer(std::vector<std::shared_ptr<vp_node_on_screen>> nodes_in_layer, cv::Mat& canvas, bool static_parts) {
std::vector<std::shared_ptr<vp_node_on_screen>> nodes_in_next_layer;
for(auto& i : nodes_in_layer) {
if (static_parts) {
i->render_static_parts(canvas);
}
else {
i->render_dynamic_parts(canvas);
}
auto n = i->get_next_nodes_on_screen();
nodes_in_next_layer.insert(nodes_in_next_layer.end(), n.begin(), n.end());
}
if (nodes_in_next_layer.size() > 0) {
bool all_the_same = true;
for(auto & i: nodes_in_next_layer) {
if (i != nodes_in_next_layer[0]) {
all_the_same = false;
break;
}
}
// just keep the first one if all the next nodes are the same
if (all_the_same) {
nodes_in_next_layer.erase(nodes_in_next_layer.begin() + 1, nodes_in_next_layer.end());
}
render_layer(nodes_in_next_layer, canvas, static_parts);
}
else { // recursion end
/* global drawing */
// draw layer index at the bottom of canvas
if (static_parts) {
for (int i = 0; i < pipe_width; i++) {
/* code */
cv::putText(canvas, "layer_" + std::to_string(i + 1), cv::Point(canvas_gap_horizontal + (node_width + node_gap_horizontal) * i, canvas_height - int(canvas_gap_vertical / 3)), 1, 1, cv::Scalar(255, 0, 0));
}
}
// refresh time & version info at the top left of canvas
if (!static_parts) {
auto time = vp_utils::time_format(NOW, "<hour>:<min>:<sec>");
cv::putText(canvas, time, cv::Point(20, 20), 1, 1, cv::Scalar(255, 0, 0));
auto version_info = APP_VERSION;
cv::putText(canvas, version_info, cv::Point(canvas.cols - 240, 20), 1, 0.8, cv::Scalar(255, 0, 0));
}
}
}
void vp_analysis_board::map_nodes(std::vector<std::shared_ptr<vp_node_on_screen>> nodes_in_layer, int layer) {
if (layer == 1) {
// here nodes_in_layer is empty
auto num_src_nodes_in_pipe = src_nodes_in_pipe.size();
auto base_left = layer_base_left_cal(layer - 1);
auto base_top = layer_base_top_cal(num_src_nodes_in_pipe);
// map nodes at 1st layer in memory to screen
for (int i = 0; i < num_src_nodes_in_pipe; i++) {
auto node_left = base_left;
auto node_top = base_top + i * (node_height + node_gap_vertical);
auto node_on_screen = std::make_shared<vp_node_on_screen>(src_nodes_in_pipe[i], vp_objects::vp_rect(node_left, node_top, node_width, node_height), layer);
src_nodes_on_screen.push_back(node_on_screen);
}
map_nodes(src_nodes_on_screen, layer + 1);
}
else {
std::vector<std::shared_ptr<vp_nodes::vp_node>> all_nodes_in_next_layer;
for(auto &i: nodes_in_layer) {
auto next_nodes = i->get_orginal_node()->next_nodes();
all_nodes_in_next_layer.insert(all_nodes_in_next_layer.end(), next_nodes.begin(), next_nodes.end());
}
if (all_nodes_in_next_layer.size() > 0) {
bool all_the_same = true;
for(auto & i: all_nodes_in_next_layer) {
if (i != all_nodes_in_next_layer[0]) {
all_the_same = false;
break;
}
}
// just keep the first one if all the next nodes are the same
if (all_the_same) {
all_nodes_in_next_layer.erase(all_nodes_in_next_layer.begin() + 1, all_nodes_in_next_layer.end());
}
auto num_all_nodes_in_next_layer = all_nodes_in_next_layer.size();
auto base_left = layer_base_left_cal(layer - 1);
auto base_top = layer_base_top_cal(num_all_nodes_in_next_layer);
auto index = 0;
std::shared_ptr<vp_node_on_screen> node_on_screen = nullptr;
std::vector<std::shared_ptr<vp_node_on_screen>> nodes_in_next_layer;
for(int i = 0; i < nodes_in_layer.size(); i++) {
auto node_left = base_left;
auto next_nodes_in_pipe = nodes_in_layer[i]->get_orginal_node()->next_nodes();
for (int j = 0; j < next_nodes_in_pipe.size(); j++)
{
auto node_top = base_top + index * (node_height + node_gap_vertical);
if (!all_the_same || node_on_screen == nullptr) {
node_on_screen = std::make_shared<vp_node_on_screen>(next_nodes_in_pipe[j], vp_objects::vp_rect(node_left, node_top, node_width, node_height), layer);
}
nodes_in_layer[i]->get_next_nodes_on_screen().push_back(node_on_screen);
if (!all_the_same || nodes_in_next_layer.empty()) {
nodes_in_next_layer.push_back(node_on_screen);
}
index++;
}
}
// next layer
map_nodes(nodes_in_next_layer, layer + 1);
}
else {
// cache the last layer
des_nodes_on_screen = nodes_in_layer;
} // recursion end
}
}
}

View File

@@ -0,0 +1,95 @@
#pragma once
#include <vector>
#include <map>
#include <mutex>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/videoio.hpp>
#include "../vp_utils.h"
#include "../../nodes/vp_node.h"
#include "../../nodes/vp_src_node.h"
#include "../../objects/vp_meta.h"
#include "../../objects/shapes/vp_rect.h"
#include "vp_node_on_screen.h"
namespace vp_utils {
class vp_analysis_board final
{
private:
// configure for render
const int node_width = 140;
const int node_height = 140;
const int canvas_gap_horizontal = 120;
const int canvas_gap_vertical = 60;
const int node_gap_horizontal = 40;
const int node_gap_vertical = 10;
std::string board_title = "vp_analysis_board";
bool alive = true;
int canvas_width = 0;
int canvas_height = 0;
std::string gst_template = "appsrc ! videoconvert ! x264enc bitrate=%d ! h264parse ! flvmux ! rtmpsink location=%s";
cv::VideoWriter rtmp_writer;
//
bool displaying = false;
// width of pipe
int pipe_width;
// height of pipe
int pipe_height;
// start points of pipe
std::vector<std::shared_ptr<vp_nodes::vp_node>> src_nodes_in_pipe;
// cache for easy access purpose
std::vector<std::shared_ptr<vp_node_on_screen>> src_nodes_on_screen;
std::vector<std::shared_ptr<vp_node_on_screen>> des_nodes_on_screen;
// canvas to draw
cv::Mat bg_canvas;
// display thread(on screen)
std::thread display_th;
// display thread(via rtmp)
std::thread rtmp_th;
// render nodes in a layer
void render_layer(std::vector<std::shared_ptr<vp_node_on_screen>> nodes_in_layer, cv::Mat& canvas, bool static_parts = true);
// map nodes in memory to screen, one layer by layer.
void map_nodes(std::vector<std::shared_ptr<vp_node_on_screen>> nodes_on_screen, int layer);
// initialize resource
void init();
// sync for reload operation
std::mutex reload_lock;
// tool methods
std::function<int(int)> layer_base_left_cal = [=](int layer_index) {return canvas_gap_horizontal + layer_index * ( node_width + node_gap_horizontal);};
std::function<int(int)> layer_base_top_cal = [=](int num_nodes_in_layer) {return (canvas_height - (num_nodes_in_layer * node_height + (num_nodes_in_layer - 1) * node_gap_vertical)) / 2; };
public:
vp_analysis_board(std::vector<std::shared_ptr<vp_nodes::vp_node>> src_nodes_in_pipe);
~vp_analysis_board();
// save pipe structure to png
void save(std::string path);
// display pipe on screen and refresh it automatically
void display(int interval = 1, bool block = true);
// display pipe by rtmp and refresh it automatically
void push_rtmp(std::string rtmp, int bitrate = 1024);
// reload pipeline with new src nodes
void reload(std::vector<std::shared_ptr<vp_nodes::vp_node>> new_src_nodes_in_pipe = std::vector<std::shared_ptr<vp_nodes::vp_node>>());
};
}

View File

@@ -0,0 +1,291 @@
#include "vp_node_on_screen.h"
namespace vp_utils {
vp_node_on_screen::vp_node_on_screen(std::shared_ptr<vp_nodes::vp_node> original_node,
vp_objects::vp_rect node_rect,
int layer):
original_node(original_node),
node_rect(node_rect),
layer(layer) {
assert(original_node != nullptr);
// register meta hookers for all nodes
original_node->set_meta_arriving_hooker([this](std::string node_name, int queue_size, std::shared_ptr<vp_objects::vp_meta> meta) {
this->meta_arriving_hooker_storage.meta = meta;
this->meta_arriving_hooker_storage.queue_size = queue_size;
this->meta_arriving_hooker_storage.called_count_since_epoch_start++;
});
original_node->set_meta_handling_hooker([this](std::string node_name, int queue_size, std::shared_ptr<vp_objects::vp_meta> meta) {
this->meta_handling_hooker_storage.meta = meta;
this->meta_handling_hooker_storage.queue_size = queue_size;
this->meta_handling_hooker_storage.called_count_since_epoch_start++;
});
original_node->set_meta_handled_hooker([this](std::string node_name, int queue_size, std::shared_ptr<vp_objects::vp_meta> meta) {
this->meta_handled_hooker_storage.meta = meta;
this->meta_handled_hooker_storage.queue_size = queue_size;
this->meta_handled_hooker_storage.called_count_since_epoch_start++;
});
original_node->set_meta_leaving_hooker([this](std::string node_name, int queue_size, std::shared_ptr<vp_objects::vp_meta> meta) {
this->meta_leaving_hooker_storage.meta = meta;
this->meta_leaving_hooker_storage.queue_size = queue_size;
this->meta_leaving_hooker_storage.called_count_since_epoch_start++;
});
// register stream info hooker if it is a src node
if (original_node->node_type() == vp_nodes::vp_node_type::SRC) {
auto src_node = std::dynamic_pointer_cast<vp_nodes::vp_src_node>(original_node);
src_node->set_stream_info_hooker([this](std::string node_name, vp_nodes::vp_stream_info stream_info) {
this->stream_info_hooker_storage = stream_info;
});
}
if (original_node->node_type() == vp_nodes::vp_node_type::DES) {
auto des_node = std::dynamic_pointer_cast<vp_nodes::vp_des_node>(original_node);
des_node->set_stream_status_hooker([this](std::string node_name, vp_nodes::vp_stream_status stream_status){
this->stream_status_hooker_storage = stream_status;
});
}
}
vp_node_on_screen::~vp_node_on_screen() {
// unregister meta hookers for all nodes
original_node->set_meta_arriving_hooker({});
original_node->set_meta_handling_hooker({});
original_node->set_meta_handled_hooker({});
original_node->set_meta_leaving_hooker({});
// unregister stream info hooker if it is a src node
if (original_node->node_type() == vp_nodes::vp_node_type::SRC) {
auto src_node = std::dynamic_pointer_cast<vp_nodes::vp_src_node>(original_node);
src_node->set_stream_info_hooker({});
}
if (original_node->node_type() == vp_nodes::vp_node_type::DES) {
auto des_node = std::dynamic_pointer_cast<vp_nodes::vp_des_node>(original_node);
des_node->set_stream_status_hooker({});
}
}
void vp_node_on_screen::render_static_parts(cv::Mat & canvas) {
auto node_left = node_rect.x;
auto node_top = node_rect.y;
auto node_width = node_rect.width;
auto node_height = node_rect.height;
cv::rectangle(canvas, cv::Rect(node_left, node_top, node_width, node_height), cv::Scalar(0, 0, 0), 1);
// node_name
vp_utils::put_text_at_center_of_rect(canvas, original_node->node_name, cv::Rect(node_left, node_top + 1, node_width, node_title_h - 2), false, font_face);
cv::line(canvas,
cv::Point(node_left, node_top + node_title_h),
cv::Point(node_left + node_width - 1, node_top + node_title_h),
cv::Scalar(0, 0, 0), 1);
// draw in_queue for non-src nodes
if (original_node->node_type() != vp_nodes::vp_node_type::SRC) {
// connect line between in_queue and out_queue
if (original_node->node_type() == vp_nodes::vp_node_type::MID) {
cv::line(canvas,
cv::Point(node_left + node_queue_width + node_queue_port_w_h, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Point(node_left + node_width / 2, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
cv::line(canvas,
cv::Point(node_left + node_width / 2, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Point(node_left + node_width / 2, node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
cv::line(canvas,
cv::Point(node_left + node_width / 2, node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h, node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
std::vector<cv::Point> vertexs {cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h, node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h * 2, node_top + node_height - node_queue_port_padding - node_queue_port_w_h),
cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h * 2, node_top + node_height - node_queue_port_padding)};
cv::fillPoly(canvas, std::vector<std::vector<cv::Point>>{vertexs}, cv::Scalar(156, 156, 156));
}
else {
// DES
cv::line(canvas,
cv::Point(node_left + node_queue_width + node_queue_port_w_h, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Point(node_left + node_width / 2, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
std::vector<cv::Point> vertexs {cv::Point(node_left + node_width / 2, node_top + node_title_h + node_queue_port_padding),
cv::Point(node_left + node_width / 2, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h),
cv::Point(node_left + node_width / 2 + node_queue_port_w_h, node_top+ node_title_h + node_queue_port_padding + node_queue_port_w_h / 2)};
cv::fillPoly(canvas, std::vector<std::vector<cv::Point>>{vertexs}, cv::Scalar(156, 156, 156));
}
cv::line(canvas,
cv::Point(node_left + node_queue_width, node_top + node_title_h),
cv::Point(node_left + node_queue_width, node_top + node_height - 1), cv::Scalar(0, 0, 0), 1);
// in port
cv::rectangle(canvas,
cv::Rect(node_left - node_queue_port_w_h + 1, node_top + node_height - node_queue_port_padding - node_queue_port_w_h, node_queue_port_w_h, node_queue_port_w_h),
cv::Scalar(156, 156, 156), 1);
// out port
cv::rectangle(canvas,
cv::Rect(node_left + node_queue_width, node_top + node_title_h + node_queue_port_padding, node_queue_port_w_h, node_queue_port_w_h),
cv::Scalar(156, 156, 156), 1);
}
// draw out_queue for non-des nodes
if (original_node->node_type() != vp_nodes::vp_node_type::DES) {
// connect line between in_queue and out_queue
if (original_node->node_type() == vp_nodes::vp_node_type::SRC) {
cv::line(canvas,
cv::Point(node_left + node_width / 2, node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h, node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
std::vector<cv::Point> vertexs {cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h, node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h * 2, node_top + node_height - node_queue_port_padding - node_queue_port_w_h),
cv::Point(node_left + node_width - node_queue_width - node_queue_port_w_h * 2, node_top + node_height - node_queue_port_padding)};
cv::fillPoly(canvas, std::vector<std::vector<cv::Point>>{vertexs}, cv::Scalar(156, 156, 156));
}
cv::line(canvas,
cv::Point(node_left + node_width - node_queue_width, node_top + node_title_h),
cv::Point(node_left + node_width - node_queue_width, node_top + node_height - 1),
cv::Scalar(0, 0, 0), 1);
// in port
cv::rectangle(canvas,
cv::Rect(node_left + node_width - node_queue_width - node_queue_port_w_h + 1, node_top + node_height - node_queue_port_padding - node_queue_port_w_h, node_queue_port_w_h, node_queue_port_w_h),
cv::Scalar(156, 156, 156), 1);
// out port
cv::rectangle(canvas,
cv::Rect(node_left + node_width -1 , node_top + node_title_h + node_queue_port_padding, node_queue_port_w_h, node_queue_port_w_h),
cv::Scalar(156, 156, 156), 1);
}
// draw blocks connect line between nodes and nodes
auto draw_connect_block = [=](int next_node_top){
cv::line(canvas,
cv::Point(node_left + node_width + node_queue_port_w_h, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Point(node_left + node_width + node_gap_horizontal / 2, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
cv::line(canvas,
cv::Point(node_left + node_width + node_gap_horizontal / 2, node_top + node_title_h + node_queue_port_padding + node_queue_port_w_h / 2),
cv::Point(node_left + node_width + node_gap_horizontal / 2, next_node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
cv::line(canvas,
cv::Point(node_left + node_width + node_gap_horizontal / 2, next_node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Point(node_left + node_width + node_gap_horizontal - node_queue_port_w_h, next_node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Scalar(156, 156, 156), 1);
std::vector<cv::Point> vertexs {cv::Point(node_left + node_width + node_gap_horizontal - node_queue_port_w_h, next_node_top + node_height - node_queue_port_padding - node_queue_port_w_h / 2),
cv::Point(node_left + node_width + node_gap_horizontal - node_queue_port_w_h * 2, next_node_top + node_height - node_queue_port_padding - node_queue_port_w_h),
cv::Point(node_left + node_width + node_gap_horizontal - node_queue_port_w_h * 2, next_node_top + node_height - node_queue_port_padding)};
cv::fillPoly(canvas, std::vector<std::vector<cv::Point>>{vertexs}, cv::Scalar(156, 156, 156));};
auto next_nodes_num = next_nodes_on_screen.size();
for (int j = 0; j < next_nodes_num; j++) {
draw_connect_block(next_nodes_on_screen[j]->node_rect.y);
}
}
void vp_node_on_screen::render_dynamic_parts(cv::Mat & canvas) {
/*
* draw data from hookers' callbacks
*/
auto fps_func = [&](vp_meta_hooker_storage& storage, cv::Rect rect) {
auto called_count = storage.called_count_since_epoch_start;
auto epoch_start = storage.time_epoch_start;
auto delta_sec = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - epoch_start);
if (delta_sec.count() > fps_timeout * 1000 || (delta_sec.count() > fps_epoch && called_count > 0)) {
//int fps = round(called_count * 1000.0 / delta_sec.count());
auto fps = vp_utils::round_any(called_count * 1000.0 / delta_sec.count(), 1);
storage.called_count_since_epoch_start = 0;
storage.time_epoch_start = std::chrono::system_clock::now();
storage.pre_fps = fps; // cache for next show
vp_utils::put_text_at_center_of_rect(canvas,
fps,
rect, true,
font_face, 1, cv::Scalar(26, 26, 139));
}
else {
// use previous fps
vp_utils::put_text_at_center_of_rect(canvas,
storage.pre_fps,
rect, true,
font_face, 1, cv::Scalar(26, 26, 139));
}
};
// non-src nodes
if (original_node->node_type() != vp_nodes::vp_node_type::SRC) {
// size of in queue
vp_utils::put_text_at_center_of_rect(canvas,
std::to_string(std::max(meta_handling_hooker_storage.queue_size, 0)),
cv::Rect(node_rect.x + 3,
node_rect.y + node_title_h / 2 + (node_rect.height - node_title_h) / 2, node_queue_width - 8, node_title_h - 10), true, font_face, 1, cv::Scalar(255, 0, 255));
// fps at 1st port
fps_func(meta_arriving_hooker_storage, cv::Rect(node_rect.x - node_queue_width / 2,
node_rect.y + node_rect.height - node_queue_port_padding * 3 - node_queue_port_w_h * 3 / 2,
node_queue_width,
node_queue_port_padding + node_queue_port_w_h));
// fps at 2nd port
fps_func(meta_handling_hooker_storage, cv::Rect(node_rect.x + node_queue_width - node_queue_width / 2,
node_rect.y + node_title_h + node_queue_port_padding * 3 / 2 + node_queue_port_w_h,
node_queue_width,
node_queue_port_padding + node_queue_port_w_h));
}
// non-des nodes
if (original_node->node_type() != vp_nodes::vp_node_type::DES) {
// size of out queue
vp_utils::put_text_at_center_of_rect(canvas,
std::to_string(std::max(meta_leaving_hooker_storage.queue_size, 0)),
cv::Rect(node_rect.x + node_rect.width - node_queue_width + 3,
node_rect.y + node_title_h / 2 + (node_rect.height - node_title_h) / 2, node_queue_width - 8, node_title_h - 10), true, font_face, 1, cv::Scalar(255, 0, 255));
// fps at 3rd port
fps_func(meta_handled_hooker_storage, cv::Rect(node_rect.x + node_rect.width - node_queue_width - node_queue_width / 2,
node_rect.y + node_rect.height - node_queue_port_padding * 3 - node_queue_port_w_h * 3 / 2,
node_queue_width,
node_queue_port_padding + node_queue_port_w_h));
// fps at 4th port
fps_func(meta_leaving_hooker_storage, cv::Rect(node_rect.x + node_rect.width - node_queue_width / 2,
node_rect.y + node_title_h + node_queue_port_padding * 3 / 2 + node_queue_port_w_h,
node_queue_width,
node_queue_port_padding + node_queue_port_w_h));
}
auto node_left = node_rect.x;
auto node_top = node_rect.y;
// stream info at src nodes
if (original_node->node_type() == vp_nodes::vp_node_type::SRC) {
vp_utils::put_text_at_center_of_rect(canvas, stream_info_hooker_storage.uri,
cv::Rect(node_left - node_rect.width * 3 / 4, node_top + node_title_h + node_queue_port_padding, node_rect.width * 4 / 3, node_title_h * 2 / 3),
true, font_face, 1, cv::Scalar(), cv::Scalar(), cv::Scalar(255, 255, 255));
vp_utils::put_text_at_center_of_rect(canvas, "original_width: " + std::to_string(stream_info_hooker_storage.original_width),
cv::Rect(node_left - node_rect.width * 3 / 4, node_top + node_title_h * 5 / 3 + node_queue_port_padding * 2, node_rect.width * 4 / 3, node_title_h * 2 / 3),
true, font_face, 1, cv::Scalar(), cv::Scalar(), cv::Scalar(255, 255, 255));
vp_utils::put_text_at_center_of_rect(canvas, "original_height: " + std::to_string(stream_info_hooker_storage.original_height),
cv::Rect(node_left - node_rect.width * 3 / 4, node_top + node_title_h * 7 / 3 + node_queue_port_padding * 3, node_rect.width * 4 / 3, node_title_h * 2 / 3),
true, font_face, 1, cv::Scalar(), cv::Scalar(), cv::Scalar(255, 255, 255));
vp_utils::put_text_at_center_of_rect(canvas, "original_fps: " + std::to_string(stream_info_hooker_storage.original_fps),
cv::Rect(node_left - node_rect.width * 3 / 4, node_top + node_title_h * 9 / 3 + node_queue_port_padding * 4, node_rect.width * 4 / 3, node_title_h * 2 / 3),
true, font_face, 1, cv::Scalar(), cv::Scalar(), cv::Scalar(255, 255, 255));
}
// stream status at des nodes
if (original_node->node_type() == vp_nodes::vp_node_type::DES) {
vp_utils::put_text_at_center_of_rect(canvas, stream_status_hooker_storage.direction,
cv::Rect(node_left + node_rect.width / 2 - 10, node_top + node_title_h * 5 / 3 + node_queue_port_padding * 2, node_rect.width * 4 / 3 + 10, node_title_h * 2 / 3),
true,font_face,1,cv::Scalar(),cv::Scalar(),cv::Scalar(255, 255, 255));
vp_utils::put_text_at_center_of_rect(canvas, "output_fps: " + vp_utils::round_any(stream_status_hooker_storage.fps, 2),
cv::Rect(node_left + node_rect.width / 2 - 10, node_top + node_title_h * 7 / 3 + node_queue_port_padding * 3, node_rect.width * 4 / 3 + 10, node_title_h * 2 / 3),
true,font_face,1,cv::Scalar(),cv::Scalar(),cv::Scalar(255, 255, 255));
vp_utils::put_text_at_center_of_rect(canvas, "latency: " + std::to_string(stream_status_hooker_storage.latency) + "ms",
cv::Rect(node_left + node_rect.width / 2 - 10, node_top + node_title_h * 9 / 3 + node_queue_port_padding * 4, node_rect.width * 4 / 3 + 10, node_title_h * 2 / 3),
true,font_face,1,cv::Scalar(),cv::Scalar(),cv::Scalar(255, 255, 255));
}
}
std::shared_ptr<vp_nodes::vp_node>& vp_node_on_screen::get_orginal_node() {
return original_node;
}
std::vector<std::shared_ptr<vp_node_on_screen>>& vp_node_on_screen::get_next_nodes_on_screen() {
return next_nodes_on_screen;
}
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include <memory>
#include <opencv2/core.hpp>
#include "../../nodes/vp_node.h"
#include "../../nodes/vp_src_node.h"
#include "../../nodes/vp_des_node.h"
#include "../../objects/shapes/vp_rect.h"
#include "../vp_utils.h"
namespace vp_utils {
// mainly used to store data from meta hookers' callback
struct vp_meta_hooker_storage {
int queue_size = -1; // size of in/out queue of node
int latency = 0; // latency(ms) relative to src node at current port
int called_count_since_epoch_start = -1; // used for calculating fps at current port
std::chrono::system_clock::time_point time_epoch_start; // used for calculating fps at current port
std::shared_ptr<vp_objects::vp_meta> meta = nullptr; // the latest meta (ptr) flowing through current port inside node (total 4 ports)
std::string pre_fps; // cache
};
// a class corresponding to vp_node, used to display node on screen and map the whole pipe from memery to screen.
class vp_node_on_screen
{
private:
// orignal node in memery
std::shared_ptr<vp_nodes::vp_node> original_node = nullptr;
// node location and size on screen
vp_objects::vp_rect node_rect;
// layer index in pipe
int layer;
// nodes in next layer on screen
std::vector<std::shared_ptr<vp_node_on_screen>> next_nodes_on_screen;
// period to calculate fps, milliseconds
int fps_epoch = 500;
// reset fps if it has been long time no update, seconds
int fps_timeout = 5;
// font for display
int font_face = cv::FONT_HERSHEY_SIMPLEX;
// container to store data from meta hookers' callbacks
vp_meta_hooker_storage meta_arriving_hooker_storage;
vp_meta_hooker_storage meta_handling_hooker_storage;
vp_meta_hooker_storage meta_handled_hooker_storage;
vp_meta_hooker_storage meta_leaving_hooker_storage;
// container to store data from stream info hooker's callback
vp_nodes::vp_stream_info stream_info_hooker_storage;
// container to store data from stream status hooker's callback
vp_nodes::vp_stream_status stream_status_hooker_storage;
// render configure
const int node_title_h = 24;
const int node_queue_width = 30;
const int node_queue_port_w_h = 6;
const int node_queue_port_padding = 8;
const int node_gap_horizontal = 40;
const int node_gap_vertical = 10;
public:
vp_node_on_screen(std::shared_ptr<vp_nodes::vp_node> original_node, vp_objects::vp_rect node_rect, int layer);
~vp_node_on_screen();
// render static parts for node, which keep unchanged all the time.
void render_static_parts(cv::Mat& canvas);
// render dynamic parts for node, which change frequently.
void render_dynamic_parts(cv::Mat& canvas);
std::shared_ptr<vp_nodes::vp_node>& get_orginal_node();
std::vector<std::shared_ptr<vp_node_on_screen>>& get_next_nodes_on_screen();
};
}