wangrong
2025-01-23 02819b5c047bb354b0ef2374e7c6a6ac4bbd5bba
nodes/vp_rtsp_ffmpeg_src_node.cpp
@@ -3,13 +3,19 @@
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/opencv.hpp>
#include "vp_rtsp_ffmpeg_src_node.h"
#include "../utils/vp_utils.h"
#include <cstdio>
#include <memory>
#include <iostream>
#include <stdexcept>
#include <iostream>
#include <vector>
#include <thread>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <cstdlib> // for setting environment variable
namespace vp_nodes
{
@@ -28,11 +34,7 @@
                                                                                   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()));
        VP_INFO(vp_utils::string_format("[%s] [%s]", node_name.c_str(), ffmpeg_format.c_str()));
        this->initialized();
    }
@@ -41,9 +43,7 @@
        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());
            std::string ffprobe_command = vp_utils::string_format(ffprobe_template, rtsp_url.c_str());
            // Execute command and capture output
            FILE *pipe = popen(ffprobe_command.c_str(), "r");
@@ -59,6 +59,7 @@
                result += buffer;
            }
            pclose(pipe);
            VP_INFO(vp_utils::string_format("FFPROBE RESULT:%s ", result));
            // Parse the output (expected format: width,height,r_frame_rate)
            std::stringstream ss(result);
@@ -70,7 +71,7 @@
            // Parse width and height
            int video_width = std::stoi(width_str);
            int video_height = std::stoi(height_str);
            int fps=25;
            int fps = 25;
            // Parse r_frame_rate (e.g., "299/12" or "25")
            if (fps_str.find('/') != std::string::npos)
@@ -86,12 +87,12 @@
                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));
                fps = static_cast<int>(std::round(static_cast<float>(numerator) / denominator));
            }
            else
            {
                // Handle integer frame rates
                int fps = std::stoi(fps_str);
                fps = std::stoi(fps_str);
            }
            // Optional: Correct minor deviations (e.g., if fps is close to 25)
@@ -102,10 +103,11 @@
            // 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));
                                            rtsp_url.c_str(), video_width, video_height, fps));
            return std::vector<int>{video_width, video_height, fps};
        }catch (...)
        }
        catch (...)
        {
            return std::vector<int>{0, 0, 0};
        }
@@ -116,38 +118,87 @@
        deinitialized();
    }
    // 子进程启动 ffmpeg 并返回管道文件描述符
    int vp_rtsp_ffmpeg_src_node::start_ffmpeg_thread(const std::string &rtsp_url, int width, int height)
    {
        int pipe_fd[2];
        if (pipe(pipe_fd) == -1)
        {
            perror("pipe");
            return -1;
        }
        pid_t pid = fork();
        if (pid == -1)
        {
            perror("fork");
            return -1;
        }
        if (pid == 0) // 子进程
        {
            close(pipe_fd[0]);               // 关闭读端
            dup2(pipe_fd[1], STDOUT_FILENO); // 将管道写端绑定到标准输出
            // 根据 channel_index 奇偶来设置 GPU
            //int gpu_id = channel_index % total_gpus; // 根据 channel_index 和 GPU 数量进行分配
            // 设置环境变量以选择 GPU
            //setenv("CUDA_VISIBLE_DEVICES", std::to_string(gpu_id).c_str(), 1);
            // 构造 ffmpeg 命令
            execlp("ffmpeg", ("ffmpeg-" + std::to_string(channel_index)).c_str(),
                   "-loglevel", "warning",
                   "-hwaccel", "cuda",
                   "-rtsp_transport", "tcp",
                   "-i", rtsp_url.c_str(),
                   "-f", ffmpeg_format.c_str(),
                   "-pix_fmt", ffmpeg_pix_fmt.c_str(),
                   "-s", (std::to_string(width) + "x" + std::to_string(height)).c_str(),
                   "pipe:1",
                   nullptr);
            exit(EXIT_FAILURE); // 若 execlp 失败
        }
        else
        {                      // 父进程
            close(pipe_fd[1]); // 关闭写端
            return pipe_fd[0]; // 返回读端文件描述符
        }
    }
    // 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];
        original_width = stream_infos[0];
        original_height = stream_infos[1];
        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;
        cv::Mat frame(original_height, original_width, CV_8UC3);
        int skip = 0;
        FILE* ffmpeg_pipe ;
        int pipe_fd = -1;
        // 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];
        int frame_size = original_width * original_height * 3; // 假设 BGR24
        std::vector<unsigned char> frame_buffer(frame_size);
        bool pipe_ok = true;
        if (use_gpu)
        {
            VP_INFO(this->ffmpeg_template);
            ffmpeg_pipe = popen(this->ffmpeg_template.c_str(), "r");
            if (!ffmpeg_pipe)
            pipe_fd = start_ffmpeg_thread(rtsp_url, original_width, original_height);
            VP_INFO(std::to_string(pipe_fd));
            if (pipe_fd < 0)
            {
                VP_WARN("Failed to start FFmpeg process, switching USING CPU.");
                VP_ERROR(vp_utils::string_format("Failed to start cuda ffmpeg thread for  [%s]", rtsp_url.c_str()));
                use_gpu = false;
            }
        }
@@ -159,7 +210,6 @@
            if (!use_gpu)
            {
                // try to open capture
                if (!rtsp_capture.isOpened())
                {
@@ -190,18 +240,60 @@
            }
            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 (!pipe_ok)  // restart pipe_fd
                {
                    pipe_fd = start_ffmpeg_thread(rtsp_url, original_width, original_height);
                }
                size_t total_read = 0;
                // 循环读取,确保一帧完整数据
                while (total_read < frame_size)
                {
                    ssize_t bytes_read = read(pipe_fd, frame_buffer.data() + total_read, frame_size - total_read);
                    if (bytes_read < 0)
                    { // 读取错误
                        if (errno == EINTR)
                        {
                            // 系统调用被中断,重试读取
                            VP_DEBUG(vp_utils::string_format("Interrupted read for  [%s],re etrying...", rtsp_url.c_str()));
                            continue;
                        }
                        else if (errno == EAGAIN)
                        {
                            // 非阻塞模式下无数据可读
                            VP_DEBUG(vp_utils::string_format("Io data available yet for  [%s],re etrying...", rtsp_url.c_str()));
                            std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 等待片刻再试
                            continue;
                        }
                        else
                        {
                            // 其他不可恢复错误,记录日志并跳过帧
                            VP_DEBUG(vp_utils::string_format("Read error for [%s].skipping frame...", rtsp_url.c_str()));
                            break;
                        }
                    }
                    else if (bytes_read == 0)
                    { // 流结束
                        VP_DEBUG(vp_utils::string_format("Stream ended for [%s].skipping frame...", rtsp_url.c_str()));
                        pipe_ok = false;
                        continue; // 结束读取
                    }
                    total_read += bytes_read;
                }
                // 如果未能成功读取完整帧,记录日志并跳过
                if (total_read < frame_size)
                {
                    VP_DEBUG(vp_utils::string_format("Incomplete frame read for [%s].skipping frame...", rtsp_url.c_str()));
                }
                // 将数据拷贝到 OpenCV Mat
                memcpy(frame.data, frame_buffer.data(), frame_size);
            }
            if (frame.empty())
@@ -232,6 +324,12 @@
            video_height = resize_frame.rows;
            this->frame_index++;
            // 插入此处
            if (frame_index % 1000 == 0) {
                VP_INFO(vp_utils::string_format(
                    "Periodic Log - Channel Index: %d, Frame Index: %d, RTSP URL: %s, Use GPU: %s",
                    channel_index, frame_index, rtsp_url.c_str(), use_gpu ? "true" : "false"));
            }
            // 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);
@@ -253,7 +351,7 @@
        }
        if (use_gpu)
        {
            fclose(ffmpeg_pipe);
            close(pipe_fd);
        }
        // send dead flag for dispatch_thread
@@ -266,4 +364,4 @@
    {
        return rtsp_url;
    }
}
}