C++, ffmpeg, libavcodec-RTSP拉流,opencv实时预览

文章目录

在ffmpeg_rtsp原有的rtsp拉流项目基础上加入了udp连接rtsp,日志模块,opencv实施预览等功能。

RTSPStreamPlayer.cpp

cpp 复制代码
#include "RTSPStreamPlayer.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <experimental/filesystem>
#include <iomanip>
#include <sstream>

namespace fs = std::experimental::filesystem;


RTSPStreamPlayer::RTSPStreamPlayer(const std::string& rtsp_url, int target_width, int target_height)
    : rtsp_url_(rtsp_url), target_width_(target_width), target_height_(target_height),
      is_running_(false), is_initialized_(false), format_context_(nullptr),
      codec_context_(nullptr), frame_(nullptr), frame_rgb_(nullptr),
      sws_context_(nullptr), buffer_(nullptr), video_stream_index_(-1),
      start_time_(-1), end_time_(-1), execution_duration_sec_(-1),
      start_time_set_(false), reconnect_attempts_(0), max_reconnect_attempts_(5),
      buffer_size_(10 * 1024 * 1024),  // 默认10MB缓冲区
      timeout_(5000000),  // 默认5秒超时
      frame_count_(0), current_fps_(0.0),
      is_recording_(false), output_format_context_(nullptr),
      output_codec_context_(nullptr), output_video_stream_(nullptr), start_pts_(0)
{}

RTSPStreamPlayer::~RTSPStreamPlayer() {
    stopRecording();
    stop();
    
    // 释放FFmpeg资源
    if (buffer_) {
        av_free(buffer_);
    }
    if (frame_rgb_) {
        av_frame_free(&frame_rgb_);
    }
    if (frame_) {
        av_frame_free(&frame_);
    }
    if (codec_context_) {
        avcodec_free_context(&codec_context_);
    }
    if (format_context_) {
        avformat_close_input(&format_context_);
    }
    if (sws_context_) {
        sws_freeContext(sws_context_);
    }
}

void RTSPStreamPlayer::setBufferSize(int buffer_size) {
    buffer_size_ = buffer_size;
}

void RTSPStreamPlayer::setTimeout(int timeout) {
    timeout_ = timeout;
}

bool RTSPStreamPlayer::init(bool use_tcp) {
    // 释放可能存在的资源
    if (format_context_) {
        avformat_close_input(&format_context_);
        format_context_ = nullptr;
    }
    
    // 初始化FFmpeg
    av_register_all();
    avformat_network_init();
    
    std::cout << "尝试初始化RTSP流: " << rtsp_url_ << std::endl;
    std::cout << "使用" << (use_tcp ? "TCP" : "UDP") << "传输协议" << std::endl;
    
    // 打开RTSP流
    format_context_ = avformat_alloc_context();
    AVDictionary *options = nullptr;
    
    // 设置传输协议
    if (use_tcp) {
        av_dict_set(&options, "rtsp_transport", "tcp", 0);
    }
    
    // 设置超时时间
    char timeout_str[20];
    snprintf(timeout_str, sizeof(timeout_str), "%d", timeout_);
    av_dict_set(&options, "stimeout", timeout_str, 0);
    
    // 设置缓冲区大小
    char buffer_size_str[20];
    snprintf(buffer_size_str, sizeof(buffer_size_str), "%d", buffer_size_);
    av_dict_set(&options, "buffer_size", buffer_size_str, 0);
    
    // 禁用多播
    av_dict_set(&options, "use_multicast", "0", 0);
    
    // 打开输入流
    int ret = avformat_open_input(&format_context_, rtsp_url_.c_str(), nullptr, &options);
    if (ret != 0) {
        char errbuf[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
        std::cerr << "无法打开RTSP流: " << errbuf << std::endl;
        av_dict_free(&options);
        return false;
    }
    av_dict_free(&options);
    
    // 获取流信息
    ret = avformat_find_stream_info(format_context_, nullptr);
    if (ret < 0) {
        char errbuf[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
        std::cerr << "无法获取流信息: " << errbuf << std::endl;
        return false;
    }
    
    // 打印流信息(调试用)
    av_dump_format(format_context_, 0, rtsp_url_.c_str(), 0);
    
    // 查找视频流
    video_stream_index_ = -1;
    for (unsigned int i = 0; i < format_context_->nb_streams; i++) {
        if (format_context_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index_ = i;
            
            // 计算并打印帧率
            AVStream* video_stream = format_context_->streams[i];
            if (video_stream->r_frame_rate.den && video_stream->r_frame_rate.num) {
                double fps = av_q2d(video_stream->r_frame_rate);
                std::cout << "视频帧率: " << fps << " FPS" << std::endl;
            } else if (video_stream->avg_frame_rate.den && video_stream->avg_frame_rate.num) {
                double fps = av_q2d(video_stream->avg_frame_rate);
                std::cout << "视频平均帧率: " << fps << " FPS" << std::endl;
            }
            
            break;
        }
    }
    
    if (video_stream_index_ == -1) {
        std::cerr << "未找到视频流" << std::endl;
        return false;
    }
    
    // 获取解码器参数
    AVCodecParameters* codec_params = format_context_->streams[video_stream_index_]->codecpar;
    std::cout << "找到视频流,编码格式: " << codec_params->codec_id << std::endl;
    
    // 获取解码器
    const AVCodec* codec = avcodec_find_decoder(codec_params->codec_id);
    if (!codec) {
        std::cerr << "无法找到解码器" << std::endl;
        return false;
    }
    
    // 初始化解码器上下文
    if (codec_context_) {
        avcodec_free_context(&codec_context_);
    }
    codec_context_ = avcodec_alloc_context3(codec);
    if (avcodec_parameters_to_context(codec_context_, codec_params) < 0) {
        std::cerr << "无法初始化解码器上下文" << std::endl;
        return false;
    }
    
    // 解码器选项
    AVDictionary *decoder_opts = nullptr;
    av_dict_set(&decoder_opts, "threads", "auto", 0);  // 自动多线程
    av_dict_set(&decoder_opts, "low_delay", "1", 0);   // 低延迟模式
    av_dict_set(&decoder_opts, "error_concealment", "1", 0);  // 错误隐藏
    
    // 打开解码器
    ret = avcodec_open2(codec_context_, codec, &decoder_opts);
    av_dict_free(&decoder_opts);
    if (ret < 0) {
        char errbuf[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
        std::cerr << "无法打开解码器: " << errbuf << std::endl;
        return false;
    }
    
    // 初始化帧
    if (frame_) av_frame_free(&frame_);
    frame_ = av_frame_alloc();
    
    if (frame_rgb_) av_frame_free(&frame_rgb_);
    frame_rgb_ = av_frame_alloc();
    
    // 分配RGB帧缓冲区
    int num_bytes = av_image_get_buffer_size(AV_PIX_FMT_BGR24, target_width_, target_height_, 1);
    if (buffer_) av_free(buffer_);
    buffer_ = (uint8_t*)av_malloc(num_bytes * sizeof(uint8_t));
    av_image_fill_arrays(frame_rgb_->data, frame_rgb_->linesize, buffer_, 
                        AV_PIX_FMT_BGR24, target_width_, target_height_, 1);
    
    // 初始化图像转换上下文
    if (sws_context_) sws_freeContext(sws_context_);
    sws_context_ = sws_getContext(
        codec_context_->width, codec_context_->height, codec_context_->pix_fmt,
        target_width_, target_height_, AV_PIX_FMT_BGR24,
        SWS_BICUBIC, nullptr, nullptr, nullptr
    );
    
    if (!sws_context_) {
        std::cerr << "无法初始化图像转换上下文" << std::endl;
        return false;
    }
    
    is_initialized_ = true;
    reconnect_attempts_ = 0;
    std::cout << "RTSP流初始化成功" << std::endl;
    return true;
}

bool RTSPStreamPlayer::reconnect() {
    if (reconnect_attempts_ >= max_reconnect_attempts_) {
        std::cerr << "已达到最大重连次数(" << max_reconnect_attempts_ << "),停止尝试" << std::endl;
        return false;
    }
    
    reconnect_attempts_++;
    std::cout << "尝试重连(" << reconnect_attempts_ << "/" << max_reconnect_attempts_ << ")..." << std::endl;
    
    // 释放当前资源
    if (codec_context_) {
        avcodec_free_context(&codec_context_);
        codec_context_ = nullptr;
    }
    if (format_context_) {
        avformat_close_input(&format_context_);
        format_context_ = nullptr;
    }
    
    // 等待一段时间再重连
    std::this_thread::sleep_for(std::chrono::seconds(2));
    
    // 尝试重新初始化
    return init(true);
}

void RTSPStreamPlayer::start() {
    if (!is_initialized_) {
        std::cerr << "请先初始化RTSP流" << std::endl;
        return;
    }
    
    is_running_ = true;
    std::thread processing_thread(&RTSPStreamPlayer::processStream, this);
    processing_thread.detach();
    
    // 显示窗口
    cv::namedWindow("RTSP Stream", cv::WINDOW_AUTOSIZE);
    
    // 显示循环
    while (is_running_) {
        cv::Mat frame;
        {
            std::lock_guard<std::mutex> lock(frame_mutex_);
            if (!current_frame_.empty()) {
                frame = current_frame_.clone();
            }
        }
        
        if (!frame.empty()) {
            cv::imshow("RTSP Stream", frame);
        }
        
        // 按键控制
        char c = (char)cv::waitKey(1);
        if (c == 27) {  // ESC键退出
            stop();
            break;
        } else if (c == 'r' || c == 'R') {  // R键开始/停止录制
            if (isRecording()) {
                stopRecording();
            } else {
                startRecording();
            }
        }
    }
    
    cv::destroyWindow("RTSP Stream");
}

void RTSPStreamPlayer::stop() {
    is_running_ = false;
}

void RTSPStreamPlayer::setTimeRange(int64_t start_sec, int64_t duration_sec) {
    std::lock_guard<std::mutex> lock(frame_mutex_);
    if (start_sec == -1) {
        start_time_ = av_gettime();
    } else {
        start_time_ = av_gettime() + start_sec * AV_TIME_BASE;
    }
    
    if (duration_sec != -1) {
        end_time_ = start_time_ + duration_sec * AV_TIME_BASE;
        execution_duration_sec_ = duration_sec;
    } else {
        end_time_ = -1;
        execution_duration_sec_ = -1;
    }
    start_time_set_ = true;
}

bool RTSPStreamPlayer::saveCurrentFrame(const std::string& filename, int quality) {
    std::lock_guard<std::mutex> lock(frame_mutex_);
    if (current_frame_.empty()) {
        return false;
    }
    
    std::vector<int> compression_params = {cv::IMWRITE_JPEG_QUALITY, quality};
    return cv::imwrite(filename, current_frame_, compression_params);
}

cv::Mat RTSPStreamPlayer::getCurrentFrame() {
    std::lock_guard<std::mutex> lock(frame_mutex_);
    return current_frame_.clone();
}

void RTSPStreamPlayer::processStream() {
    AVPacket packet;
    bool done = false;
    last_fps_calc_time_ = std::chrono::steady_clock::now();
    
    while (is_running_ && !done) {
        int ret = av_read_frame(format_context_, &packet);
        if (ret < 0) {
            char errbuf[AV_ERROR_MAX_STRING_SIZE];
            av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
            std::cerr << "读取帧失败: " << errbuf << std::endl;
            
            // 尝试重连
            if (!reconnect()) {
                done = true;
            }
            continue;
        }
        
        // 重置重连计数器
        reconnect_attempts_ = 0;
        
        if (packet.stream_index == video_stream_index_) {
            // 发送数据包到解码器
            ret = avcodec_send_packet(codec_context_, &packet);
            if (ret != 0) {
                char errbuf[AV_ERROR_MAX_STRING_SIZE];
                av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
                std::cerr << "发送数据包到解码器失败: " << errbuf << std::endl;
                av_packet_unref(&packet);
                
                // 刷新解码器
                avcodec_flush_buffers(codec_context_);
                continue;
            }
            
            // 接收解码后的帧
            while (avcodec_receive_frame(codec_context_, frame_) == 0) {
                // 计算时间戳
                int64_t pts = (frame_->pts != AV_NOPTS_VALUE) ?
                    frame_->pts * av_q2d(format_context_->streams[video_stream_index_]->time_base) * AV_TIME_BASE :
                    (frame_->pkt_dts != AV_NOPTS_VALUE ?
                        frame_->pkt_dts * av_q2d(format_context_->streams[video_stream_index_]->time_base) * AV_TIME_BASE :
                        (av_gettime() - start_time_));
                
                // 设置初始时间
                if (!start_time_set_) {
                    start_time_ = av_gettime();
                    if (execution_duration_sec_ != -1) {
                        end_time_ = start_time_ + execution_duration_sec_ * AV_TIME_BASE;
                    }
                    start_time_set_ = true;
                }
                
                // 检查是否在时间范围内
                if (isWithinTimeRange(pts)) {
                    // 转换为RGB格式
                    sws_scale(sws_context_, frame_->data, frame_->linesize, 0, 
                             codec_context_->height, frame_rgb_->data, frame_rgb_->linesize);

                    // 帧率统计
                    frame_count_++;
                    auto now = std::chrono::steady_clock::now();
                    auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - last_fps_calc_time_).count();

                    // 每1秒计算一次帧率
                    if (elapsed >= 1) {
                        current_fps_ = frame_count_ / (double)elapsed;
                        std::cout << "实际帧率: " << std::fixed << std::setprecision(2) << current_fps_ << " FPS" << std::endl;
                        
                        // 重置计数器
                        frame_count_ = 0;
                        last_fps_calc_time_ = now;
                    }
                    
                    // 转换为OpenCV的Mat
                    cv::Mat frame(target_height_, target_width_, CV_8UC3, frame_rgb_->data[0], frame_rgb_->linesize[0]);
                    
                    // 更新当前帧
                    {
                        std::lock_guard<std::mutex> lock(frame_mutex_);
                        current_frame_ = frame.clone();
                    }

                    // 录制帧(如果正在录制)
                    if (is_recording_) {
                        AVFrame* frame_av = av_frame_alloc();
                        frame_av->format = AV_PIX_FMT_BGR24;
                        frame_av->width = target_width_;
                        frame_av->height = target_height_;
                        frame_av->data[0] = frame_rgb_->data[0];
                        frame_av->linesize[0] = frame_rgb_->linesize[0];
                        
                        encodeAndWriteFrame(frame_av);
                        av_frame_free(&frame_av);
                    }
                } else if (end_time_ != -1 && pts > end_time_) {
                    done = true;
                    break;
                }
            }
        }
        
        av_packet_unref(&packet);
    }
    
    is_running_ = false;
}

bool RTSPStreamPlayer::isWithinTimeRange(int64_t pts) {
    if (end_time_ == -1) {
        return true; // 没有结束时间限制
    }
    return (pts >= start_time_ && pts <= end_time_);
}

// 初始化视频编码器
bool RTSPStreamPlayer::initVideoEncoder() {
    // 创建输出格式上下文
    avformat_alloc_output_context2(&output_format_context_, nullptr, "mp4", output_filename_.c_str());
    if (!output_format_context_) {
        std::cerr << "无法创建输出格式上下文" << std::endl;
        return false;
    }

    // 查找输出编码器
    const AVCodec* output_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!output_codec) {
        std::cerr << "找不到H264编码器" << std::endl;
        return false;
    }

    // 创建视频流
    output_video_stream_ = avformat_new_stream(output_format_context_, output_codec);
    if (!output_video_stream_) {
        std::cerr << "无法创建视频流" << std::endl;
        return false;
    }

    // 初始化解码器上下文
    output_codec_context_ = avcodec_alloc_context3(output_codec);
    if (!output_codec_context_) {
        std::cerr << "无法创建编码器上下文" << std::endl;
        return false;
    }

    // 设置编码器参数
    output_codec_context_->codec_id = output_codec->id;
    output_codec_context_->codec_type = AVMEDIA_TYPE_VIDEO;
    output_codec_context_->width = target_width_;
    output_codec_context_->height = target_height_;
    output_codec_context_->time_base = {1, 25};  // 25fps
    output_codec_context_->framerate = {25, 1};
    output_codec_context_->pix_fmt = AV_PIX_FMT_YUV420P;
    
    // H264特定设置
    if (output_codec_context_->codec_id == AV_CODEC_ID_H264) {
        av_opt_set(output_codec_context_->priv_data, "preset", "ultrafast", 0);
        av_opt_set(output_codec_context_->priv_data, "tune", "zerolatency", 0);
    }

    // 设置比特率
    output_codec_context_->bit_rate = 4000000;  // 4Mbps

    // 打开编码器
    if (avcodec_open2(output_codec_context_, output_codec, nullptr) < 0) {
        std::cerr << "无法打开编码器" << std::endl;
        return false;
    }

    // 将编码器参数复制到流
    if (avcodec_parameters_from_context(output_video_stream_->codecpar, output_codec_context_) < 0) {
        std::cerr << "无法复制编码器参数到流" << std::endl;
        return false;
    }

    // 打开输出文件
    if (!(output_format_context_->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&output_format_context_->pb, output_filename_.c_str(), AVIO_FLAG_WRITE) < 0) {
            std::cerr << "无法打开输出文件: " << output_filename_ << std::endl;
            return false;
        }
    }

    // 写入文件头
    if (avformat_write_header(output_format_context_, nullptr) < 0) {
        std::cerr << "无法写入文件头" << std::endl;
        return false;
    }

    start_pts_ = 0;
    return true;
}

// 编码并写入帧
bool RTSPStreamPlayer::encodeAndWriteFrame(AVFrame* frame) {
    if (!is_recording_ || !output_codec_context_ || !frame)
        return false;

    // 转换为YUV420P格式
    AVFrame* yuv_frame = av_frame_alloc();
    yuv_frame->format = AV_PIX_FMT_YUV420P;
    yuv_frame->width = target_width_;
    yuv_frame->height = target_height_;
    
    if (av_frame_get_buffer(yuv_frame, 0) < 0) {
        std::cerr << "无法分配帧缓冲区" << std::endl;
        av_frame_free(&yuv_frame);
        return false;
    }

    SwsContext* sws_ctx = sws_getContext(
        target_width_, target_height_, AV_PIX_FMT_BGR24,
        target_width_, target_height_, AV_PIX_FMT_YUV420P,
        SWS_BICUBIC, nullptr, nullptr, nullptr
    );

    if (!sws_ctx) {
        std::cerr << "无法创建SWS上下文" << std::endl;
        av_frame_free(&yuv_frame);
        return false;
    }

    // 转换颜色空间
    uint8_t* in_data[1] = {frame->data[0]};
    int in_linesize[1] = {frame->linesize[0]};
    sws_scale(sws_ctx, in_data, in_linesize, 0, target_height_,
              yuv_frame->data, yuv_frame->linesize);

    sws_freeContext(sws_ctx);

    // 设置时间戳
    yuv_frame->pts = start_pts_++;

    // 发送帧到编码器
    if (avcodec_send_frame(output_codec_context_, yuv_frame) < 0) {
        std::cerr << "发送帧到编码器失败" << std::endl;
        av_frame_free(&yuv_frame);
        return false;
    }

    // 接收编码后的数据包
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;

    bool success = false;
    while (avcodec_receive_packet(output_codec_context_, &pkt) == 0) {
        // 调整时间戳
        pkt.stream_index = output_video_stream_->index;
        av_packet_rescale_ts(&pkt, output_codec_context_->time_base, output_video_stream_->time_base);
        
        // 写入数据包
        if (av_interleaved_write_frame(output_format_context_, &pkt) < 0) {
            std::cerr << "写入数据包失败" << std::endl;
            break;
        }
        success = true;
    }

    av_packet_unref(&pkt);
    av_frame_free(&yuv_frame);
    return success;
}

// 开始录制
bool RTSPStreamPlayer::startRecording() {
    std::lock_guard<std::mutex> lock(recording_mutex_);
    
    if (is_recording_) {
        std::cout << "已经在录制中" << std::endl;
        return true;
    }

    // 获取当前时间字符串
    auto now = std::chrono::system_clock::now();
    std::time_t now_time = std::chrono::system_clock::to_time_t(now);
    std::tm now_tm;
#ifdef _WIN32
    localtime_s(&now_tm, &now_time);
#else
    localtime_r(&now_time, &now_tm);
#endif
    
    std::stringstream ss;
    ss << std::put_time(&now_tm, "%Y-%m-%d-%H-%M-%S");
    output_filename_ = "rtsp_" + ss.str() + ".mp4";
    
    // 初始化编码器
    if (!initVideoEncoder()) {
        std::cerr << "初始化编码器失败" << std::endl;
        return false;
    }

    is_recording_ = true;
    std::cout << "开始录制视频到: " << output_filename_ << std::endl;
    return true;
}

// 停止录制
void RTSPStreamPlayer::stopRecording() {
    std::lock_guard<std::mutex> lock(recording_mutex_);
    
    if (!is_recording_) return;

    // 刷新编码器
    if (output_codec_context_) {
        avcodec_send_frame(output_codec_context_, nullptr);
        AVPacket pkt;
        av_init_packet(&pkt);
        pkt.data = nullptr;
        pkt.size = 0;
        
        while (avcodec_receive_packet(output_codec_context_, &pkt) == 0) {
            pkt.stream_index = output_video_stream_->index;
            av_packet_rescale_ts(&pkt, output_codec_context_->time_base, output_video_stream_->time_base);
            av_interleaved_write_frame(output_format_context_, &pkt);
            av_packet_unref(&pkt);
        }
    }

    // 写入文件尾
    if (output_format_context_) {
        av_write_trailer(output_format_context_);
    }

    // 释放资源
    if (output_format_context_ && !(output_format_context_->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&output_format_context_->pb);
    }
    if (output_codec_context_) {
        avcodec_free_context(&output_codec_context_);
        output_codec_context_ = nullptr;
    }
    if (output_format_context_) {
        avformat_free_context(output_format_context_);
        output_format_context_ = nullptr;
    }
    output_video_stream_ = nullptr;

    is_recording_ = false;
    std::cout << "停止录制,视频已保存到: " << output_filename_ << std::endl;
}

// 检查是否正在录制
bool RTSPStreamPlayer::isRecording() const {
    std::lock_guard<std::mutex> lock(recording_mutex_);
    return is_recording_;
}

RTSPStreamPlayer.h

cpp 复制代码
#ifndef RTSP_STREAM_PLAYER_H
#define RTSP_STREAM_PLAYER_H

#include <string>
#include <mutex>
#include <opencv2/opencv.hpp>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
#include <libavutil/opt.h>  // 新增:用于av_opt_set
}

class RTSPStreamPlayer {
public:
    /**
     * @brief 构造函数
     * @param rtsp_url RTSP流地址
     * @param target_width 目标宽度
     * @param target_height 目标高度
     */
    RTSPStreamPlayer(const std::string& rtsp_url, int target_width = 1280, int target_height = 720);
    
    /**
     * @brief 析构函数
     */
    ~RTSPStreamPlayer();
    
    /**
     * @brief 初始化RTSP流
     * @param use_tcp 是否使用TCP传输
     * @return 成功返回true,失败返回false
     */
    bool init(bool use_tcp = true);
    
    /**
     * @brief 开始播放RTSP流
     */
    void start();
    
    /**
     * @brief 停止播放
     */
    void stop();
    
    /**
     * @brief 设置播放时间范围
     * @param start_sec 开始时间(秒),-1表示立即开始
     * @param duration_sec 持续时间(秒),-1表示无限期
     */
    void setTimeRange(int64_t start_sec = -1, int64_t duration_sec = -1);
    
    /**
     * @brief 保存当前帧为JPEG
     * @param filename 文件名
     * @param quality 质量(0-100)
     * @return 成功返回true
     */
    bool saveCurrentFrame(const std::string& filename, int quality = 95);
    
    /**
     * @brief 获取当前帧
     * @return 当前帧的拷贝
     */
    cv::Mat getCurrentFrame();

    /**
     * @brief 设置缓冲区大小
     * @param buffer_size 缓冲区大小(字节)
     */
    void setBufferSize(int buffer_size);

    /**
     * @brief 设置超时时间
     * @param timeout 超时时间(微秒)
     */
    void setTimeout(int timeout);

    /**
     * @brief 开始录制视频
     * @return 成功返回true
     */
    bool startRecording();

    /**
     * @brief 停止录制视频
     */
    void stopRecording();

    /**
     * @brief 检查是否正在录制
     * @return 正在录制返回true
     */
    bool isRecording() const;

    /**
     * @brief 获取帧互斥锁
     */
    std::mutex& getFrameMutex() { return frame_mutex_; }

private:
    std::string rtsp_url_;
    int target_width_;
    int target_height_;
    bool is_running_;
    bool is_initialized_;
    int buffer_size_;  // 缓冲区大小
    int timeout_;      // 超时时间(微秒)
    // 帧率统计
    int frame_count_;               // 帧计数器
    std::chrono::steady_clock::time_point last_fps_calc_time_;  // 上次计算帧率的时间
    double current_fps_;            // 当前帧率
    
    // FFmpeg相关变量
    AVFormatContext* format_context_;
    AVCodecContext* codec_context_;
    AVFrame* frame_;
    AVFrame* frame_rgb_;
    SwsContext* sws_context_;
    uint8_t* buffer_;
    int video_stream_index_;
    
    // 时间控制
    int64_t start_time_;
    int64_t end_time_;
    int64_t execution_duration_sec_;
    bool start_time_set_;
    
    // 当前帧和互斥锁
    cv::Mat current_frame_;
    std::mutex frame_mutex_;
    
    // 重连控制
    int reconnect_attempts_;
    const int max_reconnect_attempts_;

    // 录制相关变量
    bool is_recording_;
    std::string output_filename_;
    AVFormatContext* output_format_context_;
    AVCodecContext* output_codec_context_;
    AVStream* output_video_stream_;
    int64_t start_pts_;
    mutable std::mutex recording_mutex_;  // 修改:添加mutable关键字
    
    /**
     * @brief 处理视频流的线程函数
     */
    void processStream();
    
    /**
     * @brief 判断时间戳是否在指定范围内
     */
    bool isWithinTimeRange(int64_t pts);
    
    /**
     * @brief 尝试重新连接RTSP流
     */
    bool reconnect();

    /**
     * @brief 初始化视频编码器
     */
    bool initVideoEncoder();

    /**
     * @brief 将帧编码并写入文件
     */
    bool encodeAndWriteFrame(AVFrame* frame);
};

#endif // RTSP_STREAM_PLAYER_H

main.cpp

cpp 复制代码
#include "RTSPStreamPlayer.h"
#include <iostream>
#include <fstream>
#include <iomanip>
#include <ctime>
#include <chrono>
#include <sstream>

// 自定义流缓冲区,同时输出到终端和文件
class TeeBuf : public std::streambuf {
public:
    TeeBuf(std::streambuf* sb1, std::streambuf* sb2) : sb1_(sb1), sb2_(sb2) {}
private:
    int overflow(int c) override {
        if (c == EOF) {
            return !EOF;
        } else {
            return sb1_->sputc(c) == EOF || sb2_->sputc(c) == EOF ? EOF : c;
        }
    }
    int sync() override {
        return sb1_->pubsync() == 0 && sb2_->pubsync() == 0 ? 0 : -1;
    }
private:
    std::streambuf* sb1_;
    std::streambuf* sb2_;
};

// 获取当前时间字符串
std::string getCurrentTimeString() {
    auto now = std::chrono::system_clock::now();
    std::time_t now_time = std::chrono::system_clock::to_time_t(now);
    std::tm now_tm;
#ifdef _WIN32
    localtime_s(&now_tm, &now_time);
#else
    localtime_r(&now_time, &now_tm);
#endif
    
    std::stringstream ss;
    ss << std::put_time(&now_tm, "%Y-%m-%d-%H-%M-%S");
    return ss.str();
}

// 重定向输出到日志文件
void redirectOutputToLog() {
    std::string timeStr = getCurrentTimeString();
    std::string logFileName = "rtsp_log_" + timeStr + ".txt";
    
    static std::ofstream logFile(logFileName, std::ios::app);
    static TeeBuf teeBuf(std::cout.rdbuf(), logFile.rdbuf());
    static std::ostream tee(&teeBuf);
    
    std::cout.rdbuf(tee.rdbuf());
    std::cerr.rdbuf(tee.rdbuf());
}

int main(int argc, char* argv[]) {
    // 重定向输出到日志文件
    redirectOutputToLog();
    
    if (argc < 2) {
        std::cerr << "用法: " << argv[0] << " <RTSP_URL>" << std::endl;
        return -1;
    }
    
    // 创建RTSP播放器实例
    RTSPStreamPlayer player(argv[1], 1280, 720);
    
    // 可以根据需要调整参数
    // player.setBufferSize(5 * 1024 * 1024);  // 设置5MB缓冲区
    // player.setTimeout(10000000);  // 设置10秒超时
    
    // 尝试初始化,先尝试TCP,如果失败则尝试UDP
    bool init_success = player.init(true);
    if (!init_success) {
        std::cout << "TCP初始化失败,尝试UDP传输..." << std::endl;
        init_success = player.init(false);
    }
    
    if (!init_success) {
        std::cerr << "初始化RTSP播放器失败" << std::endl;
        return -1;
    }
    
    // 开始播放
    std::cout << "开始播放RTSP流: " << argv[1] << std::endl;
    std::cout << "按ESC键退出" << std::endl;
    std::cout << "按R键开始/停止录制视频" << std::endl;
    player.start();
    
    return 0;
}

编译

bash 复制代码
g++ main.cpp RTSPStreamPlayer.cpp -o rtsp_player `pkg-config --cflags --libs opencv4` -lavformat -lavcodec -lswscale -lavutil -lpthread

运行

bash 复制代码
./rtsp_player rtsp://10.130.209.12:8554/v

10.130.209.12是本机ip地址,可以用vlc退流进行测试:

按R开启录制。

相关推荐
John_ToDebug3 小时前
定制 ResourceBundle 的实现与 DuiLib 思想在 Chromium 架构下的应用解析
c++·chrome·ui
小欣加油4 小时前
leetcode 面试题01.02判定是否互为字符重排
数据结构·c++·算法·leetcode·职场和发展
王璐WL4 小时前
【c++】c++第一课:命名空间
数据结构·c++·算法
aramae4 小时前
C++ -- 模板
开发语言·c++·笔记·其他
Monkey的自我迭代5 小时前
多目标轮廓匹配
人工智能·opencv·计算机视觉
MChine慕青6 小时前
顺序表与单链表:核心原理与实战应用
linux·c语言·开发语言·数据结构·c++·算法·链表
骄傲的心别枯萎8 小时前
RV1126 NO.16:通过多线程同时获取H264和H265码流
linux·c++·音视频·rv1126
落羽的落羽8 小时前
【C++】特别的程序错误处理方式——异常机制
开发语言·c++
空山新雨(大队长)8 小时前
C 语言第一课:hello word c
c++·c·exe