first commit
This commit is contained in:
249
utils/analysis_board/vp_analysis_board.cpp
Executable file
249
utils/analysis_board/vp_analysis_board.cpp
Executable 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
|
||||
}
|
||||
}
|
||||
}
|
||||
95
utils/analysis_board/vp_analysis_board.h
Executable file
95
utils/analysis_board/vp_analysis_board.h
Executable 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>>());
|
||||
};
|
||||
}
|
||||
291
utils/analysis_board/vp_node_on_screen.cpp
Executable file
291
utils/analysis_board/vp_node_on_screen.cpp
Executable 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;
|
||||
}
|
||||
}
|
||||
80
utils/analysis_board/vp_node_on_screen.h
Executable file
80
utils/analysis_board/vp_node_on_screen.h
Executable 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();
|
||||
};
|
||||
}
|
||||
8234
utils/date.h
Normal file
8234
utils/date.h
Normal file
File diff suppressed because it is too large
Load Diff
69
utils/logger/README.md
Normal file
69
utils/logger/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
## summary ##
|
||||
-----------
|
||||
|
||||
### log format ###
|
||||
```
|
||||
[time][level][thread_id][code location] log_content
|
||||
| auto-generated(support config) | | content |
|
||||
```
|
||||
|
||||
### log example ###
|
||||
```
|
||||
[2022-10-21 09:47:36.835][Debug][7fff43722700][../nodes/vp_node.cpp:179] [screen_des_a] after meta flow, in_queue.size()==>12
|
||||
```
|
||||
|
||||
### tips ###
|
||||
better to add important field using `[]` in log content `Manually`, such as `module`, `type`. below code add name of host node(module) and task(type) in log content.
|
||||
|
||||
```
|
||||
VP_INFO(vp_utils::string_format("[%s] [record] save dir not exists, now creating save dir: `%s`", host_node_name, save_dir));
|
||||
```
|
||||
|
||||
### log api ###
|
||||
|
||||
#### log config ####
|
||||
```c++
|
||||
// log level
|
||||
VP_SET_LOG_LEVEL(_log_level);
|
||||
// log file dir
|
||||
VP_SET_LOG_DIR(_log_dir);
|
||||
// log kafka servers and topic
|
||||
VP_SET_LOG_KAFKA_SERVERS_AND_TOPIC(_kafka_servers_and_topic);
|
||||
|
||||
// log to console or not
|
||||
VP_SET_LOG_TO_CONSOLE(_log_to_console);
|
||||
// log to file or not
|
||||
VP_SET_LOG_TO_FILE(_log_to_file);
|
||||
// log to kafka or not
|
||||
VP_SET_LOG_TO_KAFKA(_log_to_kafka);
|
||||
|
||||
// include log level or not
|
||||
VP_SET_LOG_INCLUDE_LEVEL(_include_level);
|
||||
// include code location or not (where the log occurs)
|
||||
VP_SET_LOG_INCLUDE_CODE_LOCATION(_include_code_location);
|
||||
// include thread id or not (std::this_thread::get_id())
|
||||
VP_SET_LOG_INCLUDE_THREAD_ID(_include_thread_id);
|
||||
|
||||
// warn if log cache in memory exceed this value
|
||||
VP_SET_LOG_CACHE_WARN_THRES(_log_cache_warn_threshold);
|
||||
```
|
||||
|
||||
|
||||
#### write log ####
|
||||
4 types of log
|
||||
```c++
|
||||
// error
|
||||
VP_ERROR(message);
|
||||
// warn
|
||||
VP_WARN(message);
|
||||
// info
|
||||
VP_INFO(message);
|
||||
// debug
|
||||
VP_DEBUG(message);
|
||||
```
|
||||
|
||||
```c++
|
||||
// important! call at the begining of main()
|
||||
VP_LOGGER_INIT();
|
||||
```
|
||||
75
utils/logger/vp_log_file_writer.cpp
Normal file
75
utils/logger/vp_log_file_writer.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
|
||||
#include "vp_log_file_writer.h"
|
||||
|
||||
|
||||
namespace vp_utils {
|
||||
|
||||
vp_log_file_writer::vp_log_file_writer() {
|
||||
|
||||
}
|
||||
|
||||
vp_log_file_writer::~vp_log_file_writer() {
|
||||
if (log_writer.is_open()) {
|
||||
log_writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
void vp_log_file_writer::init(std::string log_dir, std::string log_file_name_template) {
|
||||
this->log_dir = log_dir;
|
||||
this->log_file_name_template = log_file_name_template;
|
||||
|
||||
// open log file first time
|
||||
auto f = create_valid_log_file_name();
|
||||
log_writer.open(f, std::ofstream::out | std::ofstream::app);
|
||||
|
||||
inited = true;
|
||||
}
|
||||
|
||||
|
||||
void vp_log_file_writer::write(std::string log) {
|
||||
if (!inited) {
|
||||
throw "vp_log_file_writer not initialized!";
|
||||
}
|
||||
|
||||
// check if need create new log file
|
||||
if (get_now_day() != log_day) {
|
||||
if (log_writer.is_open()) {
|
||||
log_writer.close();
|
||||
}
|
||||
|
||||
auto f = create_valid_log_file_name();
|
||||
log_writer.open(f, std::ofstream::out | std::ofstream::app);
|
||||
}
|
||||
|
||||
log_writer << log << std::endl;
|
||||
}
|
||||
|
||||
std::string vp_log_file_writer::create_valid_log_file_name() {
|
||||
if (!std::experimental::filesystem::exists(log_dir)) {
|
||||
std::experimental::filesystem::create_directories(log_dir);
|
||||
}
|
||||
std::experimental::filesystem::path root_dir(log_dir);
|
||||
|
||||
auto f_name = vp_utils::time_format(NOW, log_file_name_template);
|
||||
auto p = root_dir / f_name;
|
||||
|
||||
// cache log start day
|
||||
log_day = get_now_day();
|
||||
|
||||
return p.string();
|
||||
}
|
||||
|
||||
int vp_log_file_writer::get_now_day() {
|
||||
std::vector<int> time_parts;
|
||||
vp_utils::time_split(NOW, time_parts);
|
||||
|
||||
// refer to vp_utils::time_split(...), indice 2 is day
|
||||
return time_parts[2];
|
||||
}
|
||||
|
||||
vp_log_file_writer& vp_log_file_writer::operator<<(std::string log) {
|
||||
write(log);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
49
utils/logger/vp_log_file_writer.h
Normal file
49
utils/logger/vp_log_file_writer.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <experimental/filesystem>
|
||||
|
||||
#include "../vp_utils.h"
|
||||
|
||||
namespace vp_utils {
|
||||
// log file writer, write log to local file. auto create new log file bye date.
|
||||
// NOT thread safe, MUST use in single thread.
|
||||
class vp_log_file_writer
|
||||
{
|
||||
private:
|
||||
// ready to go
|
||||
bool inited = false;
|
||||
|
||||
// root dir for log saving
|
||||
std::string log_dir;
|
||||
// log file name template, such as `<year>-<mon>-<day>.txt`, refer to vp_utils::time_format(...) for rule details
|
||||
std::string log_file_name_template;
|
||||
|
||||
// file handle
|
||||
std::ofstream log_writer;
|
||||
// get valid log file name including path, name and extension
|
||||
std::string create_valid_log_file_name();
|
||||
|
||||
// current log day (1 ~ 31)
|
||||
int log_day = 0;
|
||||
|
||||
// get day of now
|
||||
int get_now_day();
|
||||
|
||||
#define NOW std::chrono::system_clock::now()
|
||||
public:
|
||||
vp_log_file_writer();
|
||||
~vp_log_file_writer();
|
||||
|
||||
// write log
|
||||
void write(std::string log);
|
||||
|
||||
// initialize writer
|
||||
void init(std::string log_dir, std::string log_file_name_template);
|
||||
|
||||
// for << operator
|
||||
vp_log_file_writer& operator<<(std::string log);
|
||||
};
|
||||
}
|
||||
32
utils/logger/vp_log_kafka_writer.cpp
Normal file
32
utils/logger/vp_log_kafka_writer.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifdef VP_WITH_KAFKA
|
||||
#include "vp_log_kafka_writer.h"
|
||||
|
||||
namespace vp_utils {
|
||||
|
||||
vp_log_kafka_writer::vp_log_kafka_writer(/* args */) {
|
||||
|
||||
}
|
||||
|
||||
vp_log_kafka_writer::~vp_log_kafka_writer() {
|
||||
|
||||
}
|
||||
|
||||
void vp_log_kafka_writer::write(std::string log) {
|
||||
if (!inited) {
|
||||
throw "vp_log_kafka_writer not initialized!";
|
||||
}
|
||||
kafka_producer->pushMessage(log);
|
||||
}
|
||||
|
||||
void vp_log_kafka_writer::init(std::string kafka_servers, std::string topic_name) {
|
||||
kafka_producer = std::make_shared<KafkaProducer>(kafka_servers, topic_name, 0);
|
||||
inited = true;
|
||||
}
|
||||
|
||||
// for << operator
|
||||
vp_log_kafka_writer& vp_log_kafka_writer::operator<<(std::string log) {
|
||||
write(log);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
31
utils/logger/vp_log_kafka_writer.h
Normal file
31
utils/logger/vp_log_kafka_writer.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef VP_WITH_KAFKA
|
||||
#include <memory>
|
||||
// to-do: refactor for file structure
|
||||
#include "../../nodes/broker/kafka_utils/KafkaProducer.h"
|
||||
|
||||
namespace vp_utils {
|
||||
class vp_log_kafka_writer
|
||||
{
|
||||
private:
|
||||
// ready to go
|
||||
bool inited = false;
|
||||
// wrapper producer
|
||||
std::shared_ptr<KafkaProducer> kafka_producer = nullptr;
|
||||
|
||||
public:
|
||||
vp_log_kafka_writer(/* args */);
|
||||
~vp_log_kafka_writer();
|
||||
|
||||
// write log
|
||||
void write(std::string log);
|
||||
|
||||
// initialize writer
|
||||
void init(std::string kafka_servers, std::string topic_name);
|
||||
|
||||
// for << operator
|
||||
vp_log_kafka_writer& operator<<(std::string log);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
161
utils/logger/vp_logger.cpp
Normal file
161
utils/logger/vp_logger.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
|
||||
#include "vp_logger.h"
|
||||
|
||||
namespace vp_utils {
|
||||
|
||||
vp_logger::vp_logger(/* args */)
|
||||
{
|
||||
}
|
||||
|
||||
vp_logger::~vp_logger() {
|
||||
die();
|
||||
if (log_writer_th.joinable()) {
|
||||
log_writer_th.join();
|
||||
}
|
||||
}
|
||||
|
||||
void vp_logger::die() {
|
||||
alive = false;
|
||||
std::lock_guard<std::mutex> guard(log_cache_mutex);
|
||||
log_cache.push("die");
|
||||
log_cache_semaphore.signal();
|
||||
}
|
||||
|
||||
void vp_logger::init() {
|
||||
inited = true;
|
||||
|
||||
// initialize file writer
|
||||
file_writer.init(log_dir, log_file_name_template);
|
||||
|
||||
#ifdef VP_WITH_KAFKA
|
||||
// initialize kafka writer
|
||||
auto servers_and_topic = vp_utils::string_split(kafka_servers_and_topic, '/');
|
||||
assert(servers_and_topic.size() == 2);
|
||||
kafka_writer.init(servers_and_topic[0], servers_and_topic[1]);
|
||||
#endif
|
||||
|
||||
// run thread
|
||||
auto t = std::thread(&vp_logger::log_write_run, this);
|
||||
log_writer_th = std::move(t);
|
||||
}
|
||||
|
||||
void vp_logger::log(vp_log_level level, const std::string& message, const char* code_file, int code_line) {
|
||||
// make sure logger is initialized
|
||||
if (!inited) {
|
||||
throw "vp_logger is not initialized yet!";
|
||||
}
|
||||
|
||||
// level filter
|
||||
if (level > log_level) {
|
||||
return;
|
||||
}
|
||||
|
||||
// keywords filter for debug level
|
||||
if (level == vp_log_level::DEBUG && keywords_for_debug_log.size() != 0) {
|
||||
bool filterd = true;
|
||||
for(auto& keywords: keywords_for_debug_log) {
|
||||
if (message.find(keywords) != std::string::npos) {
|
||||
filterd = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (filterd) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* create log */
|
||||
std::string new_log = "";
|
||||
// 100% true for log time
|
||||
if (include_time) {
|
||||
new_log += vp_utils::time_format(NOW, log_time_templete);
|
||||
}
|
||||
|
||||
// log level
|
||||
if (include_level) {
|
||||
new_log += "[" + log_level_names.at(level) + "]";
|
||||
}
|
||||
|
||||
// thread id
|
||||
if (include_thread_id) {
|
||||
auto id = std::this_thread::get_id();
|
||||
std::stringstream ss;
|
||||
ss << std::hex << id; // to hex
|
||||
auto thread_id = ss.str();
|
||||
new_log += "[" + thread_id + "]";
|
||||
}
|
||||
|
||||
// code location
|
||||
if (include_code_location) {
|
||||
new_log += "[" + std::string(code_file) + ":" + std::to_string(code_line) + "]";
|
||||
}
|
||||
|
||||
new_log += " " + message;
|
||||
|
||||
/* write to cache */
|
||||
// min lock range
|
||||
std::lock_guard<std::mutex> guard(log_cache_mutex);
|
||||
log_cache.push(new_log);
|
||||
// notify
|
||||
log_cache_semaphore.signal();
|
||||
}
|
||||
|
||||
void vp_logger::log_write_run() {
|
||||
bool log_thres_warned = false;
|
||||
/* below code runs in single thread */
|
||||
while (inited && alive) {
|
||||
// wait for data
|
||||
log_cache_semaphore.wait();
|
||||
auto log = log_cache.front();
|
||||
log_cache.pop();
|
||||
|
||||
if (log == "die") {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* watch the log cache size */
|
||||
auto log_cache_size = 0;
|
||||
{
|
||||
// min lock range
|
||||
std::lock_guard<std::mutex> guard(log_cache_mutex);
|
||||
log_cache_size = log_cache.size();
|
||||
}
|
||||
if (!log_thres_warned && log_cache_size > log_cache_warn_threshold) {
|
||||
VP_WARN(vp_utils::string_format("[logger] log cache size is exceeding threshold! cache size is: [%d], threshold is: [%d]", log_cache_size, log_cache_warn_threshold));
|
||||
log_thres_warned = true; // warn 1 time
|
||||
}
|
||||
if (log_cache_size <= log_cache_warn_threshold) {
|
||||
log_thres_warned = false;
|
||||
}
|
||||
|
||||
/* write to devices */
|
||||
if (log_to_console) {
|
||||
write_to_console(log);
|
||||
}
|
||||
|
||||
if (log_to_file) {
|
||||
write_to_file(log);
|
||||
}
|
||||
|
||||
if (log_to_kafka) {
|
||||
write_to_kafka(log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vp_logger::write_to_console(const std::string& log) {
|
||||
std::cout << log << std::endl;
|
||||
}
|
||||
|
||||
void vp_logger::write_to_file(const std::string& log) {
|
||||
// file_writer.write(log);
|
||||
file_writer << log;
|
||||
}
|
||||
|
||||
void vp_logger::write_to_kafka(const std::string& log) {
|
||||
#ifdef VP_WITH_KAFKA
|
||||
// kafka_writer.write(log);
|
||||
kafka_writer << log;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
139
utils/logger/vp_logger.h
Normal file
139
utils/logger/vp_logger.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
|
||||
#include "../vp_semaphore.h"
|
||||
#include "../vp_utils.h"
|
||||
#include "vp_log_file_writer.h"
|
||||
#include "vp_log_kafka_writer.h"
|
||||
|
||||
namespace vp_utils {
|
||||
// log levels
|
||||
enum vp_log_level {
|
||||
ERROR = 1,
|
||||
WARN = 2,
|
||||
INFO = 3,
|
||||
DEBUG = 4
|
||||
};
|
||||
|
||||
// a lightweight logger for VideoPipe, architecture: N producer * 1 consumer.
|
||||
// 1. support 3 types of devices (console, file, kafka)
|
||||
// 2. multithread safe
|
||||
// 3. use Macros directly
|
||||
class vp_logger
|
||||
{
|
||||
private:
|
||||
std::queue<std::string> log_cache;
|
||||
vp_utils::vp_semaphore log_cache_semaphore;
|
||||
std::mutex log_cache_mutex;
|
||||
std::thread log_writer_th;
|
||||
|
||||
// initialized or not
|
||||
bool inited = false;
|
||||
|
||||
bool alive = true;
|
||||
void die();
|
||||
|
||||
vp_logger(/* args */);
|
||||
|
||||
// write to devices
|
||||
void write_to_console(const std::string& log);
|
||||
void write_to_file(const std::string& log);
|
||||
void write_to_kafka(const std::string& log);
|
||||
|
||||
// writing log func
|
||||
void log_write_run();
|
||||
|
||||
// file writer
|
||||
vp_utils::vp_log_file_writer file_writer;
|
||||
|
||||
#ifdef VP_WITH_KAFKA
|
||||
// kafka writer
|
||||
vp_utils::vp_log_kafka_writer kafka_writer;
|
||||
#endif
|
||||
|
||||
const std::map<vp_log_level, std::string> log_level_names = {{ERROR, "Error"},
|
||||
{WARN, "Warn "},
|
||||
{INFO, "Info "},
|
||||
{DEBUG, "Debug"}};
|
||||
public:
|
||||
// non-copable
|
||||
vp_logger(const vp_logger&) = delete;
|
||||
vp_logger& operator=(const vp_logger&) = delete;
|
||||
|
||||
// singleton
|
||||
static vp_logger& get_logger() {
|
||||
static vp_logger logger;
|
||||
return logger;
|
||||
}
|
||||
~vp_logger();
|
||||
|
||||
// CONFIG
|
||||
vp_log_level log_level = vp_log_level::DEBUG; // filter
|
||||
std::string log_dir = "./log"; // folder saving log file
|
||||
std::string kafka_servers_and_topic = "127.0.0.1:9092/vp_log"; // kafka servers and topic, splited by `/`. multiple servers splited by `,`
|
||||
const std::string log_file_name_template = "<year>-<mon>-<day>.txt";
|
||||
const std::string log_time_templete = "[<year>-<mon>-<day> <hour>:<min>:<sec>.<mili>]";
|
||||
|
||||
/*
|
||||
* keywords as filters for the log of debug level since it always writes too frequently, empty means no filters.
|
||||
* use the name of a node is a normal method to apply this function,
|
||||
* `file_src_0` means write all debug log generated by the node named file_src_0.
|
||||
*/
|
||||
std::vector<std::string> keywords_for_debug_log = {};
|
||||
|
||||
// watch
|
||||
int log_cache_warn_threshold = 100; // warning if cache size greater than threshold
|
||||
|
||||
// where
|
||||
bool log_to_console = true; // to console
|
||||
bool log_to_file = true; // to file
|
||||
bool log_to_kafka = false; // to kafka
|
||||
|
||||
// how
|
||||
const bool include_time = true; // `log time` part in log content (readonly)
|
||||
bool include_level = true; // `log level` part in log content
|
||||
bool include_code_location = true; // `log from` part in log content
|
||||
bool include_thread_id = true; // 'thread id' part in log content
|
||||
// END of CONFIG
|
||||
|
||||
// better Never call directly
|
||||
void log(vp_log_level level, const std::string& message, const char* code_file, int code_line);
|
||||
|
||||
// init for vp_logger, ready to go
|
||||
void init();
|
||||
};
|
||||
|
||||
// config Macros
|
||||
#define VP_SET_LOG_LEVEL(_log_level) vp_utils::vp_logger::get_logger().log_level = _log_level
|
||||
#define VP_SET_LOG_DIR(_log_dir) vp_utils::vp_logger::get_logger().log_dir = _log_dir
|
||||
#define VP_SET_LOG_KAFKA_SERVERS_AND_TOPIC(_kafka_servers_and_topic) vp_utils::vp_logger::get_logger().kafka_servers_and_topic = _kafka_servers_and_topic
|
||||
#define VP_SET_LOG_TO_CONSOLE(_log_to_console) vp_utils::vp_logger::get_logger().log_to_console = _log_to_console
|
||||
#define VP_SET_LOG_TO_FILE(_log_to_file) vp_utils::vp_logger::get_logger().log_to_console = _log_to_file
|
||||
#define VP_SET_LOG_TO_KAFKA(_log_to_kafka) vp_utils::vp_logger::get_logger().log_to_kafka = _log_to_kafka
|
||||
#define VP_SET_LOG_INCLUDE_LEVEL(_include_level) vp_utils::vp_logger::get_logger().include_level = _include_level
|
||||
#define VP_SET_LOG_INCLUDE_CODE_LOCATION(_include_code_location) vp_utils::vp_logger::get_logger().include_code_location = _include_code_location
|
||||
#define VP_SET_LOG_INCLUDE_THREAD_ID(_include_thread_id) vp_utils::vp_logger::get_logger().include_thread_id = _include_thread_id
|
||||
#define VP_SET_LOG_CACHE_WARN_THRES(_log_cache_warn_threshold) vp_utils::vp_logger::get_logger().log_cache_warn_threshold = _log_cache_warn_threshold
|
||||
#define VP_SET_LOG_KEYWORDS_FOR_DEBUG(_keywords_for_debug_log) vp_utils::vp_logger::get_logger().keywords_for_debug_log = _keywords_for_debug_log
|
||||
|
||||
// log Macros
|
||||
// use vp_utils::string_format to format log content first if need
|
||||
// example:
|
||||
// VP_ERROR(vp_utils::string_format("message is %s at %d", s, d));
|
||||
#define VP_ERROR(message) vp_utils::vp_logger::get_logger().log(vp_utils::vp_log_level::ERROR, message, __FILE__, __LINE__)
|
||||
#define VP_WARN(message) vp_utils::vp_logger::get_logger().log(vp_utils::vp_log_level::WARN, message, __FILE__, __LINE__)
|
||||
#define VP_INFO(message) vp_utils::vp_logger::get_logger().log(vp_utils::vp_log_level::INFO, message, __FILE__, __LINE__)
|
||||
#define VP_DEBUG(message) vp_utils::vp_logger::get_logger().log(vp_utils::vp_log_level::DEBUG, message, __FILE__, __LINE__)
|
||||
|
||||
// init Macros
|
||||
#define VP_LOGGER_INIT() vp_utils::vp_logger::get_logger().init()
|
||||
|
||||
#define NOW std::chrono::system_clock::now()
|
||||
}
|
||||
41
utils/vp_gate.h
Normal file
41
utils/vp_gate.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace vp_utils {
|
||||
// semaphore used to resume/pause loop structure, it blocks thread while received unactive signal and unblock thread while received active signal.
|
||||
// refer to vp_semaphore also
|
||||
class vp_gate
|
||||
{
|
||||
public:
|
||||
vp_gate() {
|
||||
opened_ = false;
|
||||
}
|
||||
|
||||
void open() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
opened_ = true;
|
||||
cv_.notify_one();
|
||||
}
|
||||
|
||||
// wait until opened
|
||||
void knock() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
cv_.wait(lock, [=] { return opened_; });
|
||||
}
|
||||
|
||||
void close() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
opened_ = false;
|
||||
}
|
||||
|
||||
bool is_open() {
|
||||
return opened_;
|
||||
}
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
bool opened_;
|
||||
};
|
||||
}
|
||||
180
utils/vp_pipe_checker.h
Executable file
180
utils/vp_pipe_checker.h
Executable file
@@ -0,0 +1,180 @@
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
#include "vp_version.h"
|
||||
#include "../nodes/vp_node.h"
|
||||
#include "../excepts/vp_invalid_pipeline_error.h"
|
||||
|
||||
namespace vp_utils {
|
||||
class vp_pipe_checker
|
||||
{
|
||||
private:
|
||||
// we can only handle the pipeline structure like below, totally symmetrical vertically for each layer.
|
||||
/*
|
||||
* |1st layer| |2nd layer| |3rd layer| |4th layer| |5th layer|
|
||||
* node -...- node \ / node -...- node
|
||||
*
|
||||
* node -...- node - -...- node -...- node -...- node
|
||||
*
|
||||
* node -...- node / \ node -...- node
|
||||
*
|
||||
*/
|
||||
void check_layer(std::vector<std::shared_ptr<vp_nodes::vp_node>> nodes_in_layer) {
|
||||
num_layers_in_pipe++;
|
||||
|
||||
int next_nodes_num = -1;
|
||||
std::vector<std::shared_ptr<vp_nodes::vp_node>> all_next_nodes;
|
||||
|
||||
// check for:
|
||||
// 1. node type of start/end point of pipe
|
||||
// 2. node names in pipe
|
||||
// 3. next nodes number
|
||||
for(auto& i : nodes_in_layer) {
|
||||
// check for the first layer, all nodes MUST be vp_nodes::vp_node_type::SRC
|
||||
if (num_layers_in_pipe == 1 &&
|
||||
i->node_type() != vp_nodes::vp_node_type::SRC) {
|
||||
throw vp_excepts::vp_invalid_pipeline_error("pipe MUST starts with SRC nodes!");
|
||||
}
|
||||
|
||||
auto name = i->node_name;
|
||||
// check if node name duplicated
|
||||
if (std::find_if(std::begin(node_names_in_pipe),
|
||||
std::end(node_names_in_pipe),
|
||||
[&](std::pair<int, std::vector<std::string>> p){ return std::find(std::begin(p.second), std::end(p.second), name) != std::end(p.second);})
|
||||
!=
|
||||
std::end(node_names_in_pipe)) {
|
||||
throw vp_excepts::vp_invalid_pipeline_error("node names MUST not be duplicated in pipe!");
|
||||
}
|
||||
node_names_in_pipe[num_layers_in_pipe].push_back(name);
|
||||
|
||||
auto next_nodes = i->next_nodes();
|
||||
auto _num = next_nodes.size();
|
||||
|
||||
// check for the last layer, all nodes MUST be vp_nodes::vp_node_type::DES
|
||||
if (_num == 0 && i->node_type() != vp_nodes::vp_node_type::DES) {
|
||||
throw vp_excepts::vp_invalid_pipeline_error("pipe MUST ends with DES nodes!");
|
||||
}
|
||||
|
||||
// initialize next_nodes_num variable, all nodes in current layer should have the same next_nodes_num.
|
||||
if (next_nodes_num == -1) {
|
||||
next_nodes_num = _num;
|
||||
}
|
||||
|
||||
// the next nodes number of each node in the same layer must be equal (all are 0 for des nodes).
|
||||
if (next_nodes_num != _num) {
|
||||
throw vp_excepts::vp_invalid_pipeline_error("all nodes in the same layer MUST have the same next nodes number!");
|
||||
}
|
||||
|
||||
// merge next nodes of each node into a big vector
|
||||
all_next_nodes.insert(all_next_nodes.end(), next_nodes.begin(), next_nodes.end());
|
||||
}
|
||||
|
||||
// check for 2 conditions:
|
||||
// 1. all next nodes are the same, or
|
||||
// 2. all next nodes are distinc.
|
||||
if (all_next_nodes.size() != 0) {
|
||||
bool all_the_same = true;
|
||||
bool all_distinct = true;
|
||||
// check if all the next nodes are the same
|
||||
for(auto & i: all_next_nodes) {
|
||||
if (i != all_next_nodes[0])
|
||||
{
|
||||
all_the_same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if all next nodes are distinct
|
||||
if (!all_the_same) {
|
||||
std::sort(std::begin(all_next_nodes), std::end(all_next_nodes));
|
||||
auto pos = std::adjacent_find(std::begin(all_next_nodes), std::end(all_next_nodes));
|
||||
if (pos != std::end(all_next_nodes)) {
|
||||
all_distinct = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!all_the_same && !all_distinct) {
|
||||
throw vp_excepts::vp_invalid_pipeline_error("the next nodes of each node in the same layer MUST be the same or be distinct totally!");
|
||||
}
|
||||
|
||||
// just keep the first one if all the next nodes are the same
|
||||
if (all_the_same) {
|
||||
all_next_nodes.erase(all_next_nodes.begin() + 1, all_next_nodes.end());
|
||||
}
|
||||
|
||||
// recursion for next layer
|
||||
check_layer(all_next_nodes);
|
||||
}
|
||||
}
|
||||
// layer_index -> node_names
|
||||
std::map<int, std::vector<std::string>> node_names_in_pipe;
|
||||
int num_layers_in_pipe = 0;
|
||||
bool summary;
|
||||
public:
|
||||
vp_pipe_checker(bool summary = true) {
|
||||
summary = summary;
|
||||
}
|
||||
|
||||
// total layers
|
||||
int pipe_width() {
|
||||
return num_layers_in_pipe;
|
||||
}
|
||||
|
||||
// max number of nodes in all layers
|
||||
int pipe_height() {
|
||||
int number = 0;
|
||||
for(auto & i : node_names_in_pipe) {
|
||||
if (i.second.size() > number) {
|
||||
number = i.second.size();
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
void operator()(std::vector<std::shared_ptr<vp_nodes::vp_node>> pipe_src_nodes) {
|
||||
// start check with the 1st layer
|
||||
check_layer(pipe_src_nodes);
|
||||
|
||||
if (summary) {
|
||||
/*
|
||||
std::cout << "############# pipe check summary ##############" << std::endl;
|
||||
std::cout << " total layers: " << num_layers_in_pipe << std::endl;
|
||||
std::cout << " layer index, node names" << std::endl;
|
||||
for(auto & i : node_names_in_pipe) {
|
||||
std::cout << " " << i.first << " ";
|
||||
for(auto & j : i.second) {
|
||||
std::cout << j << ",";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << ############# pipe check summary ############## << std::endl;
|
||||
*/
|
||||
|
||||
std::ostringstream v_stream;
|
||||
v_stream << "\n################ version info #################\n";
|
||||
v_stream << " build_time: " << APP_BUILD_TIME << "\n";
|
||||
v_stream << " commit_hash: " << APP_GIT_COMMIT << "\n";
|
||||
v_stream << " version: " << APP_VERSION << "\n";
|
||||
v_stream << "################ version info ################" << "\n";
|
||||
// to log
|
||||
VP_INFO(v_stream.str());
|
||||
|
||||
std::ostringstream s_stream;
|
||||
s_stream << "\n############# pipe check summary ##############\n";
|
||||
s_stream << " total layers: " << num_layers_in_pipe << "\n";
|
||||
s_stream << " layer index, node names" << "\n";
|
||||
for(auto & i : node_names_in_pipe) {
|
||||
s_stream << " " << i.first << " ";
|
||||
for(auto & j : i.second) {
|
||||
s_stream << j << ",";
|
||||
}
|
||||
s_stream << "\n";
|
||||
}
|
||||
s_stream << "############# pipe check summary ##############" << "\n";
|
||||
|
||||
// to log
|
||||
VP_INFO(s_stream.str());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
39
utils/vp_semaphore.h
Executable file
39
utils/vp_semaphore.h
Executable file
@@ -0,0 +1,39 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace vp_utils {
|
||||
// semaphore for queue/deque data structures in VideoPipe, used for producer-consumer pattern.
|
||||
// it blocks the consumer thread until data has come.
|
||||
class vp_semaphore
|
||||
{
|
||||
public:
|
||||
vp_semaphore() {
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
void signal() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
++count_;
|
||||
cv_.notify_one();
|
||||
}
|
||||
|
||||
void wait() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
cv_.wait(lock, [=] { return count_ > 0; });
|
||||
--count_;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
count_ = 0;
|
||||
cv_.notify_one();
|
||||
}
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
int count_;
|
||||
};
|
||||
}
|
||||
193
utils/vp_utils.h
Executable file
193
utils/vp_utils.h
Executable file
@@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
|
||||
// https://github.com/HowardHinnant/date/blob/master/include/date/date.h
|
||||
#include "date.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace vp_utils {
|
||||
// string format in C++17
|
||||
template<typename ... Args>
|
||||
inline string string_format(const string& format, Args ... args){
|
||||
size_t size = 1 + snprintf(nullptr, 0, format.c_str(), args ...);
|
||||
// unique_ptr<char[]> buf(new char[size]);
|
||||
char bytes[size];
|
||||
snprintf(bytes, size, format.c_str(), args ...);
|
||||
return string(bytes);
|
||||
}
|
||||
|
||||
// get optimal font scale depend on screen width and height
|
||||
inline double get_optimal_font_scale(string text, int screen_width, int screen_height, cv::Size & text_size, int fontface = cv::FONT_HERSHEY_PLAIN, int thickness = 1) {
|
||||
int baseline = 0;
|
||||
for (int i = 20; i > 0; i--) {
|
||||
auto size = cv::getTextSize(text, fontface, i / 20.0, thickness, &baseline);
|
||||
if (size.width < screen_width && size.height < screen_height) {
|
||||
text_size = size;
|
||||
return i / 20.0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// draw rounded rectangle, fill the backgound.
|
||||
inline void draw_rounded_rectangle(cv::Mat& src, cv::Point top_left, cv::Point bottom_right, int corner_radius, cv::Scalar color = cv::Scalar(), int thickness = 1, cv::Scalar fill_color = cv::Scalar(255, 255, 255), int line_type = cv::LINE_4)
|
||||
{
|
||||
/* corners:
|
||||
* p1 - p2
|
||||
* | |
|
||||
* p4 - p3
|
||||
*/
|
||||
if (corner_radius < 1) {
|
||||
corner_radius = 1;
|
||||
}
|
||||
|
||||
cv::Point p1 = top_left;
|
||||
cv::Point p2 = cv::Point (bottom_right.x, top_left.y);
|
||||
cv::Point p3 = bottom_right;
|
||||
cv::Point p4 = cv::Point (top_left.x, bottom_right.y);
|
||||
|
||||
// fill rounded rectangle
|
||||
if (thickness < 0) {
|
||||
cv::rectangle(src, cv::Rect(cv::Point (p1.x + corner_radius, p1.y), cv::Point (p3.x - corner_radius, p3.y)), fill_color, thickness);
|
||||
cv::rectangle(src, cv::Rect(cv::Point (p1.x, p1.y + corner_radius), cv::Point (p3.x, p3.y - corner_radius)), fill_color, thickness);
|
||||
cv::ellipse( src, p1 + cv::Point(corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 180.0, 0, 90, fill_color, thickness, line_type );
|
||||
cv::ellipse( src, p2 + cv::Point(-corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 270.0, 0, 90, fill_color, thickness, line_type );
|
||||
cv::ellipse( src, p3 + cv::Point(-corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 0.0, 0, 90, fill_color, thickness, line_type );
|
||||
cv::ellipse( src, p4 + cv::Point(corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 90.0, 0, 90, fill_color, thickness, line_type );
|
||||
|
||||
thickness = 1;
|
||||
}
|
||||
|
||||
// draw straight lines
|
||||
cv::line(src, cv::Point (p1.x + corner_radius, p1.y), cv::Point (p2.x - corner_radius, p2.y), color, thickness, line_type);
|
||||
cv::line(src, cv::Point (p2.x, p2.y + corner_radius), cv::Point (p3.x, p3.y - corner_radius), color, thickness, line_type);
|
||||
cv::line(src, cv::Point (p4.x + corner_radius, p4.y), cv::Point (p3.x - corner_radius, p3.y), color, thickness, line_type);
|
||||
cv::line(src, cv::Point (p1.x, p1.y + corner_radius), cv::Point (p4.x, p4.y - corner_radius), color, thickness, line_type);
|
||||
|
||||
// draw arcs
|
||||
cv::ellipse( src, p1 + cv::Point(corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 180.0, 0, 90, color, thickness, line_type );
|
||||
cv::ellipse( src, p2 + cv::Point(-corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 270.0, 0, 90, color, thickness, line_type );
|
||||
cv::ellipse( src, p3 + cv::Point(-corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 0.0, 0, 90, color, thickness, line_type );
|
||||
cv::ellipse( src, p4 + cv::Point(corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 90.0, 0, 90, color, thickness, line_type );
|
||||
}
|
||||
|
||||
// put text at the center of specific rect on canvas
|
||||
inline void put_text_at_center_of_rect(cv::Mat& canvas, string text, cv::Rect rect, bool fill = false, int font_face = cv::FONT_HERSHEY_PLAIN, int thickness = 1, cv::Scalar color = cv::Scalar(), cv::Scalar border_color = cv::Scalar(201, 201, 205), cv::Scalar fill_color = cv::Scalar(201, 201, 205), int padding = 1) {
|
||||
cv::Size text_size;
|
||||
auto font_scale = get_optimal_font_scale(text, rect.width - padding * 2, rect.height - padding * 2, text_size, font_face, thickness);
|
||||
if (fill) {
|
||||
draw_rounded_rectangle(canvas, cv::Point(rect.x, rect.y), cv::Point(rect.x + rect.width, rect.y + rect.height), 3, border_color, -1, fill_color);
|
||||
}
|
||||
cv::putText(canvas,
|
||||
text,
|
||||
cv::Point(rect.x + (rect.width - text_size.width) / 2, rect.y + rect.height - (rect.height - text_size.height) / 2),
|
||||
font_face, font_scale, color);
|
||||
}
|
||||
|
||||
// use std::string::end_with(...) in standard library directly for C++20
|
||||
inline bool ends_with(std::string const & value, std::string const & ending) {
|
||||
if (ending.size() > value.size()) return false;
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
}
|
||||
|
||||
// round double data using specific precision, return string.
|
||||
inline std::string round_any(double input, int precision) {
|
||||
input = input * std::pow(10, precision) + 0.5;
|
||||
auto output = std::to_string(std::floor(input) / std::pow(10, precision));
|
||||
|
||||
output.erase(std::find_if(output.rbegin(), output.rend(), [](unsigned char ch) {
|
||||
return ch != '0';
|
||||
}).base(), output.end());
|
||||
|
||||
// remove the last point `.` if possible
|
||||
if (ends_with(output, ".")) {
|
||||
output.pop_back();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> string_split(const std::string& s, char delimiter) {
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
std::istringstream tokenStream(s);
|
||||
while (std::getline(tokenStream, token, delimiter)) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// split time point to 7 parts, include year, month, day ... in order
|
||||
inline void time_split(system_clock::time_point tp, std::vector<int>& time_parts, int time_zone = 8) {
|
||||
// right time zone
|
||||
tp = tp + std::chrono::hours{time_zone};
|
||||
|
||||
auto dp = date::floor<date::days>(tp);
|
||||
|
||||
auto ymd = date::year_month_day{dp};
|
||||
auto time = date::make_time(std::chrono::duration_cast<std::chrono::milliseconds>(tp - dp));
|
||||
|
||||
time_parts.clear();
|
||||
|
||||
// push year/month/day
|
||||
time_parts.push_back(static_cast<int>(ymd.year()));
|
||||
time_parts.push_back(static_cast<unsigned>(ymd.month()));
|
||||
time_parts.push_back(static_cast<unsigned>(ymd.day()));
|
||||
|
||||
// push hour/minute/second/milisecond
|
||||
time_parts.push_back(time.hours().count());
|
||||
time_parts.push_back(time.minutes().count());
|
||||
time_parts.push_back(time.seconds().count());
|
||||
time_parts.push_back(time.subseconds().count());
|
||||
}
|
||||
|
||||
// set width and fill, like ostream.fill() and ostream.width()
|
||||
inline std::string set_width_and_fill(std::string str,
|
||||
int width,
|
||||
char fill = '0',
|
||||
bool fill_left = true) {
|
||||
while (str.size() < width) {
|
||||
if (fill_left) {
|
||||
str = fill + str;
|
||||
}
|
||||
else {
|
||||
str = str + fill;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// format time point to string, chars in <> are keywords.
|
||||
// in: <year>-<mon>-<day> <hour>:<min>:<sec>.<mili>
|
||||
// out: 2022-10-08 13:53:07.230
|
||||
// or
|
||||
// in: [<day>/<mon>/<year> <hour>:<min>:<sec>.<mili>]
|
||||
// out: [08/10/2022 13:53:07.230]
|
||||
inline std::string time_format(system_clock::time_point tp, std::string template_str = "<year>-<mon>-<day> <hour>:<min>:<sec>.<mili>", int time_zone = 8) {
|
||||
// right time zone
|
||||
tp = tp + std::chrono::hours{time_zone};
|
||||
|
||||
// 7 parts
|
||||
std::vector<int> time_parts;
|
||||
time_split(tp, time_parts, 0);
|
||||
|
||||
auto time_str = std::regex_replace(template_str, std::regex("<year>"), std::to_string(time_parts[0]));
|
||||
time_str = std::regex_replace(time_str, std::regex("<mon>"), set_width_and_fill(std::to_string(time_parts[1]), 2));
|
||||
time_str = std::regex_replace(time_str, std::regex("<day>"), set_width_and_fill(std::to_string(time_parts[2]), 2));
|
||||
time_str = std::regex_replace(time_str, std::regex("<hour>"), set_width_and_fill(std::to_string(time_parts[3]), 2));
|
||||
time_str = std::regex_replace(time_str, std::regex("<min>"), set_width_and_fill(std::to_string(time_parts[4]), 2));
|
||||
time_str = std::regex_replace(time_str, std::regex("<sec>"), set_width_and_fill(std::to_string(time_parts[5]), 2));
|
||||
time_str = std::regex_replace(time_str, std::regex("<mili>"), set_width_and_fill(std::to_string(time_parts[6]), 3));
|
||||
|
||||
return time_str;
|
||||
}
|
||||
|
||||
}
|
||||
5
utils/vp_version.h.in
Normal file
5
utils/vp_version.h.in
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define APP_BUILD_TIME "@BUILD_TIME@"
|
||||
#define APP_GIT_COMMIT "@FINAL_GIT_VERSION@"
|
||||
#define APP_VERSION "v@BUILD_TIME@-@FINAL_GIT_VERSION@"
|
||||
Reference in New Issue
Block a user