|
|
#include <opencv2/core/core.hpp>
|
#include <opencv2/imgproc.hpp>
|
#include <opencv2/videoio.hpp>
|
|
#include "vp_rtsp_ffmpeg_src_node.h"
|
#include "../utils/vp_utils.h"
|
#include <cstdio>
|
#include <memory>
|
#include <iostream>
|
#include <stdexcept>
|
|
namespace vp_nodes
|
{
|
vp_rtsp_ffmpeg_src_node::vp_rtsp_ffmpeg_src_node(std::string node_name,
|
int channel_index,
|
std::string rtsp_url,
|
float resize_ratio,
|
int skip_interval,
|
bool use_gpu,
|
std::string ffmpeg_format,
|
std::string ffmpeg_pix_fmt) : vp_src_node(node_name, channel_index, resize_ratio),
|
rtsp_url(rtsp_url),
|
skip_interval(skip_interval),
|
use_gpu(use_gpu),
|
ffmpeg_format(ffmpeg_format),
|
ffmpeg_pix_fmt(ffmpeg_pix_fmt)
|
{
|
assert(skip_interval >= 0 && skip_interval <= 9);
|
if (use_gpu)
|
{
|
this->ffmpeg_template = vp_utils::string_format(this->ffmpeg_template, rtsp_url.c_str(), ffmpeg_format.c_str(), ffmpeg_pix_fmt.c_str(), std::to_string(100 + channel_index).c_str());
|
}
|
VP_INFO(vp_utils::string_format("[%s] [%s]", node_name.c_str(), ffmpeg_template.c_str()));
|
this->initialized();
|
}
|
|
std::vector<int> vp_rtsp_ffmpeg_src_node::initialize_stream_properties()
|
{
|
try
|
{
|
// FFmpeg command to probe stream metadata
|
std::string ffprobe_command = vp_utils::string_format(
|
"ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate -of csv=p=0 %s",
|
rtsp_url.c_str());
|
|
// Execute command and capture output
|
FILE *pipe = popen(ffprobe_command.c_str(), "r");
|
if (!pipe)
|
{
|
throw std::runtime_error("Failed to execute ffprobe command.");
|
}
|
|
char buffer[128];
|
std::string result = "";
|
while (fgets(buffer, sizeof(buffer), pipe) != nullptr)
|
{
|
result += buffer;
|
}
|
pclose(pipe);
|
|
// Parse the output (expected format: width,height,r_frame_rate)
|
std::stringstream ss(result);
|
std::string width_str, height_str, fps_str;
|
std::getline(ss, width_str, ',');
|
std::getline(ss, height_str, ',');
|
std::getline(ss, fps_str, ',');
|
|
// Parse width and height
|
int video_width = std::stoi(width_str);
|
int video_height = std::stoi(height_str);
|
int fps=25;
|
|
// Parse r_frame_rate (e.g., "299/12" or "25")
|
if (fps_str.find('/') != std::string::npos)
|
{
|
// Handle fractional frame rates
|
std::stringstream fps_ss(fps_str);
|
std::string numerator_str, denominator_str;
|
std::getline(fps_ss, numerator_str, '/');
|
std::getline(fps_ss, denominator_str, '/');
|
|
// Convert to integer values
|
int numerator = std::stoi(numerator_str);
|
int denominator = std::stoi(denominator_str);
|
|
// Calculate and round to the nearest integer
|
int fps = static_cast<int>(std::round(static_cast<float>(numerator) / denominator));
|
}
|
else
|
{
|
// Handle integer frame rates
|
int fps = std::stoi(fps_str);
|
}
|
|
// Optional: Correct minor deviations (e.g., if fps is close to 25)
|
if (std::abs(fps - 25) < 1)
|
{
|
fps = 25; // Force fps to 25 if very close
|
}
|
|
// Debug log for parsed properties
|
VP_INFO(vp_utils::string_format("Stream URL:%s Stream properties - Width: %d, Height: %d, FPS: %d",
|
rtsp_url, video_width, video_height, fps));
|
|
return std::vector<int>{video_width, video_height, fps};
|
}catch (...)
|
{
|
return std::vector<int>{0, 0, 0};
|
}
|
}
|
|
vp_rtsp_ffmpeg_src_node::~vp_rtsp_ffmpeg_src_node()
|
{
|
deinitialized();
|
}
|
|
// define how to read video from rtsp stream, create frame meta etc.
|
// please refer to the implementation of vp_node::handle_run.
|
void vp_rtsp_ffmpeg_src_node::handle_run()
|
{
|
|
std::vector<int> stream_infos = initialize_stream_properties();
|
|
int original_width = stream_infos[0];
|
int original_height = stream_infos[1];
|
int original_fps = stream_infos[2];
|
|
int video_width = original_width;
|
int video_height = original_height;
|
// set true fps because skip some frames
|
int fps = original_fps / (skip_interval + 1);
|
|
cv::Mat frame;
|
|
int skip = 0;
|
FILE* ffmpeg_pipe ;
|
|
// Create a pipe buffer to read raw data from FFmpeg
|
size_t frame_size = original_height * original_width * 3; // Adjust this based on expected frame size (e.g., 1920x1080 with RGB)
|
unsigned char *frame_buffer = new unsigned char[frame_size];
|
|
if (use_gpu)
|
{
|
VP_INFO(this->ffmpeg_template);
|
ffmpeg_pipe = popen(this->ffmpeg_template.c_str(), "r");
|
if (!ffmpeg_pipe)
|
{
|
VP_WARN("Failed to start FFmpeg process, switching USING CPU.");
|
use_gpu = false;
|
}
|
}
|
|
while (alive)
|
{
|
// check if need workc
|
gate.knock();
|
|
if (!use_gpu)
|
{
|
|
// try to open capture
|
if (!rtsp_capture.isOpened())
|
{
|
if (!rtsp_capture.open(this->rtsp_url, cv::CAP_FFMPEG))
|
{
|
VP_WARN(vp_utils::string_format("[%s] open rtsp failed, try again...", node_name.c_str()));
|
continue;
|
}
|
}
|
if (video_width == 0 || video_height == 0 || fps == 0)
|
{
|
video_width = rtsp_capture.get(cv::CAP_PROP_FRAME_WIDTH);
|
video_height = rtsp_capture.get(cv::CAP_PROP_FRAME_HEIGHT);
|
fps = rtsp_capture.get(cv::CAP_PROP_FPS);
|
|
original_fps = fps;
|
original_width = video_width;
|
original_height = video_height;
|
|
// set true fps because skip some frames
|
fps = fps / (skip_interval + 1);
|
}
|
// stream_info_hooker activated if need
|
vp_stream_info stream_info{channel_index, original_fps, original_width, original_height, to_string()};
|
invoke_stream_info_hooker(node_name, stream_info);
|
|
rtsp_capture >> frame;
|
}
|
else
|
{
|
// Read the raw video data from the pipe into a frame
|
if (fread(frame_buffer, sizeof(unsigned char), frame_size, ffmpeg_pipe) <= 0)
|
{
|
VP_WARN("Failed to read frame from FFmpeg pipe, retrying...");
|
continue;
|
}
|
// stream_info_hooker activated if need
|
vp_stream_info stream_info{channel_index, original_fps, original_width, original_height, to_string()};
|
invoke_stream_info_hooker(node_name, stream_info);
|
|
// Convert the raw video data into an OpenCV Mat object
|
frame = cv::Mat(original_height, original_width, CV_8UC3, frame_buffer);
|
}
|
|
if (frame.empty())
|
{
|
VP_WARN(vp_utils::string_format("[%s] reading frame empty, total frame==>%d", node_name.c_str(), frame_index));
|
continue;
|
}
|
|
// need skip
|
if (skip < skip_interval)
|
{
|
skip++;
|
continue;
|
}
|
skip = 0;
|
|
cv::Mat resize_frame;
|
if (this->resize_ratio != 1.0f)
|
{
|
cv::resize(frame, resize_frame, cv::Size(), resize_ratio, resize_ratio);
|
}
|
else
|
{
|
resize_frame = frame.clone(); // clone!;
|
}
|
// set true size because resize
|
video_width = resize_frame.cols;
|
video_height = resize_frame.rows;
|
|
this->frame_index++;
|
// create frame meta
|
auto out_meta =
|
std::make_shared<vp_objects::vp_frame_meta>(resize_frame, this->frame_index, this->channel_index, video_width, video_height, fps);
|
|
if (out_meta != nullptr)
|
{
|
this->out_queue.push(out_meta);
|
|
// handled hooker activated if need
|
if (this->meta_handled_hooker)
|
{
|
meta_handled_hooker(node_name, out_queue.size(), out_meta);
|
}
|
|
// important! notify consumer of out_queue in case it is waiting.
|
this->out_queue_semaphore.signal();
|
VP_DEBUG(vp_utils::string_format("[%s] after handling meta, out_queue.size()==>%d", node_name.c_str(), out_queue.size()));
|
}
|
}
|
if (use_gpu)
|
{
|
fclose(ffmpeg_pipe);
|
}
|
|
// send dead flag for dispatch_thread
|
this->out_queue.push(nullptr);
|
this->out_queue_semaphore.signal();
|
}
|
|
// return stream url
|
std::string vp_rtsp_ffmpeg_src_node::to_string()
|
{
|
return rtsp_url;
|
}
|
}
|