C++, ffmpeg, libavcodec-RTSP拉流,opencv實時預覽

文章目錄

      • RTSPStreamPlayer.cpp
      • RTSPStreamPlayer.h
      • main.cpp
      • 編譯
      • 運行

在ffmpeg_rtsp原有的rtsp拉流項目基礎上加入了udp連接rtsp,日志模塊,opencv實施預覽等功能。

RTSPStreamPlayer.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;}// 初始化FFmpegav_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的Matcv::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};  // 25fpsoutput_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 _WIN32localtime_s(&now_tm, &now_time);
#elselocaltime_r(&now_time, &now_tm);
#endifstd::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

#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_;/*** @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

#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 _WIN32localtime_s(&now_tm, &now_time);
#elselocaltime_r(&now_time, &now_tm);
#endifstd::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,如果失敗則嘗試UDPbool 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;
}

編譯

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

運行

./rtsp_player rtsp://10.130.209.12:8554/v

10.130.209.12是本機ip地址,可以用vlc退流進行測試:
在這里插入圖片描述
在這里插入圖片描述
按R開啟錄制,實測拉流速度要比 vlc 快一秒。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/922297.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/922297.shtml
英文地址,請注明出處:http://en.pswp.cn/news/922297.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL在Ubuntu 20.04 環境下的卸載與安裝

目錄 前言&#xff1a;學習引入 1、安裝注意事項 2、學習建議 3、MySQL 和 MariaDB 核心概念一&#xff1a;它們是什么&#xff1f; 核心概念二&#xff1a;它們如何工作&#xff1f;&#xff08;“倉庫”比喻&#xff09; 核心概念三&#xff1a;為什么它們如此流行&…

BizDevOps 是什么?如何建設企業 BizDevOps 體系

在數字經濟加速滲透的今天&#xff0c;企業數字化轉型已從 “技術升級” 轉向 “價值重構”&#xff0c;單純的 IT 研發或業務優化已難以適應市場快速變化。業務研發運營一體化&#xff08;BizDevOps&#xff09;作為打通 “業務 - 技術 - 運維” 協同壁壘的核心模式&#xff0…

Mac菜單欄綜合工具FancyTool更新啦

本次更新聚焦「輕量體驗」深度優化&#xff1a;不僅重構了 CPU 占用邏輯與系統喚醒機制&#xff0c;讓后臺運行更高效&#xff1b;更讓動畫交互全程保持絲滑流暢&#xff0c;資源消耗卻低到近乎無感 —— 哪怕它常駐菜單欄&#xff0c;你也幾乎察覺不到它的存在&#xff0c;既不…

ARM匯編 led

1.相關介紹本次用的開發板是IMX6ULLCPU&#xff1a;NXP i.MX 6ULL Cortex-A7單核處理器&#xff0c;主頻 528MHz&#xff08;工業級&#xff09; 或 800MHz&#xff08;商業級&#xff09;467, GBA封裝內存&#xff1a;512MB DDR3L RAM&#xff0c;支持高速數據存取。存儲&…

彈窗分頁保留其他頁面勾選的數據(vue)

如圖所示&#xff0c;這是個常見的多選todolist不過這里多了個要求&#xff0c;彈窗上下頁面切換的時候需要保留勾選結果這其實也不難&#xff0c;但是如果每次都手動寫一遍卻有點惱人&#xff0c;這次捋一下思路&#xff0c;并把核心代碼記錄一下&#xff0c;方便下次翻找核心…

分享:一種為藍牙、WIFI、U段音頻發射設備提供ARC回傳數字音頻橋接功能的方案

隨著智能電視、流媒體設備的普及&#xff0c;用戶對高質量音頻輸出的需求激增。為解決多設備協同、無線化傳輸及ARC高保真音頻傳輸的痛點&#xff0c;納祥科技推出HDMI ARC音頻轉換方案&#xff1a;HDMI ARC音頻轉光纖/同軸/I2S/左右聲道&#xff0c;橋接無線音頻發射設備&…

在WPF項目中使用阿里圖標庫iconfont

使用阿里圖標庫的步驟&#xff1a; 1。從阿里圖標庫官方網站上下載圖標。 2。把阿里圖標庫&#xff08;WPF中支持.ttf字體文件&#xff09;引入 3。在App.xaml中添加圖標的全局樣式。推薦在此處添加全局樣式&#xff0c;為了保證圖標可以在所有窗體中使用。 代碼如下&#x…

vue3項目啟動流程講解

Vue 3 項目啟動流程詳解Vue 3 項目的啟動流程相比 Vue 2 有了顯著變化&#xff0c;采用了新的應用實例創建方式和組合式 API。下面我將詳細講解 Vue 3 項目的啟動過程&#xff0c;并提供一個可視化演示。實現思路創建 Vue 3 應用實例配置根組件和必要的插件掛載應用到 DOM展示啟…

【C++】LLVM-mingw + VSCode:Windows 開發攻略

LLVM-mingw 是一個基于 LLVM 項目的開源工具鏈&#xff0c;用于在類 Unix 系統&#xff08;如 Linux 或 macOS&#xff09;上為 Windows 平臺交叉編譯應用程序&#xff0c;它結合了 LLVM 編譯器基礎設施&#xff08;包括 Clang C/C/Objective-C 編譯器和 LLD 鏈接器&#xff0c…

AI內容標識新規實施后,大廠AI用戶協議有何變化?(六)科大訊飛

科大訊飛也是國產老將&#xff0c;當年OpenAI橫空出世&#xff0c;國內唯有文心和星火能與之一戰&#xff0c;早期效果感覺甚至是優于文心的&#xff0c;只是后面再也沒有什么大動靜出來。訊飛也算大廠了&#xff0c;但跟百度阿里這些老牌互聯網門閥相比&#xff0c;還是不夠持…

Error: MiniProgramError{“errMsg“:“navigateTo:fail webview count limit exceed“}

這個錯誤 "navigateTo:fail webview count limit exceed" 是微信小程序中常見的頁面棧溢出問題&#xff0c;原因是微信小程序對頁面棧深度有默認限制&#xff08;通常為10層&#xff09;&#xff0c;當使用 navigateTo 連續跳轉頁面導致頁面棧超過限制時就會觸發。解…

少即是多:從 MPTCP 看優化干預的邊界

“對待端到端傳輸&#xff0c;信息不足就要少干預&#xff0c;越干預越糟糕”&#xff0c;這是我的信條&#xff0c;這次再來說說 MPTCP。 Linux 內核 MPTCP 最好的調度算法就是 default 算法&#xff0c;沒有之一&#xff0c;因為它以代價最小&#xff0c;最自然的方式做到了保…

“開源AI智能名片鏈動2+1模式S2B2C商城小程序”在直播公屏引流中的應用與效果

摘要&#xff1a;本文聚焦于直播公屏引流場景&#xff0c;探討“開源AI智能名片鏈動21模式S2B2C商城小程序”如何通過技術賦能與模式創新&#xff0c;重構直播電商的流量獲取與轉化路徑。研究結合案例分析與實證數據&#xff0c;揭示該方案在提升用戶互動、優化供應鏈管理、降低…

基于大數據挖掘的藥品不良反應知識整合與利用研究

標題:基于大數據挖掘的藥品不良反應知識整合與利用研究內容:1.摘要 隨著醫療數據的爆炸式增長&#xff0c;大數據挖掘技術在醫療領域的應用日益廣泛。本研究旨在利用大數據挖掘技術對藥品不良反應知識進行整合與利用&#xff0c;以提高藥品安全性監測和管理水平。通過收集多源異…

國產時序數據庫選型指南-從大數據視角看透的價值

摘要&#xff1a;大數據時代時序數據庫崛起&#xff0c;工業物聯網場景下每秒百萬級數據點寫入成為常態。Apache IoTDB憑借單節點1000萬點/秒的寫入性能、毫秒級查詢響應和20:1超高壓縮比脫穎而出&#xff0c;其樹形數據模型完美適配工業設備層級結構。相比傳統數據庫&#xff…

教你使用服務器如何搭建數據庫

數據庫是存儲和管理數據的核心組件&#xff0c;無論是網站、應用還是企業系統&#xff0c;都離不開數據庫的支持。本文將以 萊卡云服務器 為例&#xff0c;教你如何快速搭建常用數據庫服務。一、準備工作服務器環境推薦操作系統&#xff1a;Ubuntu 20.04 / Debian 11 / CentOS …

西門子 S7-200 SMART PLC 核心指令詳解:從移位、上升沿和比較指令到流水燈控制程序實戰

對于 PLC 初學者來說&#xff0c;“流水燈” 是繞不開的經典入門案例 —— 它看似簡單&#xff0c;卻濃縮了 PLC 編程的核心邏輯&#xff1a;初始化、時序控制、指令應用與狀態判斷。今天我們就以 S7-200 SMART 為例&#xff0c;逐行拆解一段 8 位流水燈控制程序&#xff0c;帶…

P4342 [IOI 1998] Polygon -普及+/提高

P4342 [IOI 1998] Polygon 題目描述 題目可能有些許修改&#xff0c;但大意一致。 Polygon 是一個玩家在一個有 nnn 個頂點的多邊形上玩的游戲&#xff0c;如圖所示&#xff0c;其中 n4n 4n4。每個頂點用整數標記&#xff0c;每個邊用符號 &#xff08;加&#xff09;或符號 *…

枚舉算法和排序算法能力測試

枚舉算法題目 1&#xff1a;找出 1-20 中既是偶數又是 3 的倍數的數題目描述&#xff1a;小明想找出 1 到 20 中既能被 2 整除又能被 3 整除的數字&#xff0c;幫他列出來吧。 代碼&#xff1a;cpp運行#include <iostream> using namespace std; int main() {int a;for (…

大數據電商流量分析項目實戰:Hadoop初認識+ HA環境搭建(二)

?博客主頁&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客內容》&#xff1a;大數據、Java、測試開發、Python、Android、Go、Node、Android前端小程序等相關領域知識 &#x1f4e2;博客專欄&#xff1a; https://blog.csdn.net/m0_63815035/…