文章目录
在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开启录制。