C++ opencv RTSP小工具 RTSP流播放、每一帧保存

目录

效果

项目

代码

下载


效果

项目

代码

#include <opencv2/opencv.hpp>

#include <iostream>

#include <chrono>

#include <iomanip>

#include <sstream>

#include <fstream>

#include <string>

#include <thread>

#include <atomic>

#include <queue>

#include <mutex>

#include <condition_variable>

#include <sys/stat.h> // 用于文件夹操作 (C++14兼容)

#include <direct.h> // Windows下的mkdir

// 线程安全的帧队列

class FrameQueue {

private:

std::queue<std::pair<cv::Mat, std::string>> queue;

std::mutex mtx;

std::condition_variable cond;

bool stop_flag = false;

public:

void push(const cv::Mat& frame, const std::string& filename) {

std::unique_lock<std::mutex> lock(mtx);

// 深拷贝帧,因为原帧可能会被修改或释放

queue.push(std::make_pair(frame.clone(), filename));

cond.notify_one();

}

bool pop(std::pair<cv::Mat, std::string>& item) {

std::unique_lock<std::mutex> lock(mtx);

cond.wait(lock, [this]() { return !queue.empty() || stop_flag; });

if (stop_flag && queue.empty()) {

return false;

}

item = queue.front();

queue.pop();

return true;

}

void stop() {

std::unique_lock<std::mutex> lock(mtx);

stop_flag = true;

cond.notify_all();

}

bool empty() {

std::unique_lock<std::mutex> lock(mtx);

return queue.empty();

}

};

// 全局变量

FrameQueue frameQueue;

std::atomic<long long> total_save_time{ 0 };

std::atomic<int> saved_frames_count{ 0 };

std::string imgFolder = "img"; // 图片保存文件夹

// 检查文件夹是否存在 (C++14兼容方法)

bool folderExists(const std::string& folderPath) {

struct stat info;

return stat(folderPath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);

}

// 创建文件夹 (C++14兼容方法)

bool createFolder(const std::string& folderPath) {

#ifdef _WIN32

return _mkdir(folderPath.c_str()) == 0;

#else

return mkdir(folderPath.c_str(), 0733) == 0;

#endif

}

// 检查并创建图片保存文件夹

bool ensureImageFolderExists() {

// 检查文件夹是否存在

if (folderExists(imgFolder)) {

std::cout << "使用图片保存文件夹: " << imgFolder << std::endl;

return true;

}

// 如果不存在,尝试创建文件夹

if (createFolder(imgFolder)) {

std::cout << "创建图片保存文件夹: " << imgFolder << std::endl;

return true;

}

std::cerr << "错误: 无法创建图片保存文件夹" << std::endl;

return false;

}

// 保存图像的线程函数

void saveFrameThread() {

std::vector<int> compression_params;

compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);

compression_params.push_back(100);

while (true) {

std::pair<cv::Mat, std::string> item;

if (!frameQueue.pop(item)) {

break;

}

auto start_time = std::chrono::high_resolution_clock::now();

bool saveResult = cv::imwrite(item.second, item.first);

//bool saveResult = cv::imwrite(item.second, item.first, compression_params);

auto end_time = std::chrono::high_resolution_clock::now();

auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

if (!saveResult) {

std::cerr << "错误: 无法保存帧: " << item.second << std::endl;

}

else {

long long save_time = duration.count();

total_save_time += save_time;

saved_frames_count++;

std::cout << "帧已保存: " << item.second<< " (保存时间: " << save_time << "ms)" << std::endl;

}

}

}

// 保存图像的线程函数

void saveFrameThread2() {

std::vector<int> compression_params;

compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);

compression_params.push_back(100); // 质量

while (true) {

std::pair<cv::Mat, std::string> item;

if (!frameQueue.pop(item)) {

break;

}

auto start_time = std::chrono::high_resolution_clock::now();

// 编码图像

std::vector<uchar> buffer;

auto encode_start = std::chrono::high_resolution_clock::now();

cv::imencode(".jpg", item.first, buffer, compression_params);

auto encode_end = std::chrono::high_resolution_clock::now();

// 写入文件

auto write_start = std::chrono::high_resolution_clock::now();

std::ofstream ofs(item.second, std::ios::binary);

ofs.write(reinterpret_cast<char*>(buffer.data()), buffer.size());

ofs.close();

auto write_end = std::chrono::high_resolution_clock::now();

auto end_time = std::chrono::high_resolution_clock::now();

auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

auto encode_duration = std::chrono::duration_cast<std::chrono::milliseconds>(encode_end - encode_start);

auto write_duration = std::chrono::duration_cast<std::chrono::milliseconds>(write_end - write_start);

if (!ofs) {

std::cerr << "错误: 无法保存帧: " << item.second << std::endl;

}

else {

long long total_time = total_duration.count();

long long encode_time = encode_duration.count();

long long write_time = write_duration.count();

total_save_time += total_time;

saved_frames_count++;

std::cout << "帧已保存: " << item.second

<< " (总时间: " << total_time << "ms, 编码: " << encode_time << "ms, 写入: " << write_time << "ms)" << std::endl;

}

}

}

// 显示使用说明

void printUsage(const std::string& programName) {

std::cout << "使用方法: " << programName << " <RTSP_URL>" << std::endl;

std::cout << "示例: " << programName << " rtsp://username:password@192.168.1.100:554/stream" << std::endl;

std::cout << "注意: 如果URL中包含特殊字符(如&),请将整个URL用双引号括起来" << std::endl;

std::cout << std::endl;

std::cout << "控制命令:" << std::endl;

std::cout << " q/Q - 退出程序" << std::endl;

std::cout << " 空格键 - 暂停/继续播放" << std::endl;

std::cout << " s/S - 显示保存统计信息" << std::endl;

std::cout << " c/C - 清零统计信息" << std::endl;

}

int main(int argc, char* argv[]) {

// 检查命令行参数

if (argc != 2) {

std::cerr << "错误: 需要提供RTSP URL作为参数" << std::endl;

printUsage(argv[0]);

return -1;

}

// 从命令行参数获取RTSP URL

std::string rtspUrl = argv[1];

std::cout << "使用RTSP URL: " << rtspUrl << std::endl;

// 检查并创建图片保存文件夹

if (!ensureImageFolderExists()) {

std::cerr << "错误: 无法创建或访问图片保存文件夹,程序将退出" << std::endl;

return -1;

}

// 打开视频流

cv::VideoCapture cap(rtspUrl, cv::CAP_FFMPEG);

if (!cap.isOpened()) {

std::cerr << "错误: 无法打开RTSP流" << std::endl;

return -1;

}

// 获取视频流的基本信息

double fps = cap.get(cv::CAP_PROP_FPS);

int frameWidth = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));

int frameHeight = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));

std::cout << "RTSP流信息: " << frameWidth << "x" << frameHeight << " at " << fps << " FPS" << std::endl;

// 创建保存线程

std::thread saveThread(saveFrameThread);

// 创建窗口

cv::namedWindow("RTSP Stream", cv::WINDOW_NORMAL);

cv::resizeWindow("RTSP Stream", frameWidth / 4, frameHeight / 4);

// 主循环

cv::Mat frame;

while (true) {

// 读取一帧

if (!cap.read(frame)) {

std::cerr << "错误: 读取帧失败" << std::endl;

break;

}

// 显示帧

cv::imshow("RTSP Stream", frame);

// 获取当前时间(精确到毫秒)

auto now = std::chrono::system_clock::now();

auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);

auto epoch = now_ms.time_since_epoch();

auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);

long long milliseconds = value.count();

// 将毫秒时间戳转换为可读格式

std::time_t time_t_now = std::chrono::system_clock::to_time_t(now);

std::tm tm_struct;

localtime_s(&tm_struct, &time_t_now);

std::ostringstream oss;

oss << std::put_time(&tm_struct, "%Y%m%d_%H%M%S_") << std::setfill('0') << std::setw(3) << (milliseconds % 1000);

std::string timestampStr = oss.str();

// 构建保存图像的文件名(包含img文件夹路径)

std::string filename = imgFolder + "/frame_" + timestampStr + ".bmp";

// 将帧和文件名添加到队列(由保存线程处理)

frameQueue.push(frame, filename);

// 处理键盘输入

int key = cv::waitKey(1) & 0xFF;

if (key == 'q' || key == 'Q') {

break;

}

else if (key == ' ') {

while (true) {

int innerKey = cv::waitKey(0) & 0xFF;

if (innerKey == ' ') {

break;

}

else if (innerKey == 'q' || innerKey == 'Q') {

cv::destroyAllWindows();

cap.release();

frameQueue.stop();

saveThread.join();

return 0;

}

}

}

else if (key == 's' || key == 'S') {

// 显示统计信息

if (saved_frames_count > 0) {

double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;

std::cout << "保存统计: " << saved_frames_count << " 帧, 平均保存时间: "<< avg_save_time << "ms" << std::endl;

}

}

else if (key == 'c' || key == 'C') {

// 清空统计信息

total_save_time = 0;

saved_frames_count = 0;

std::cout << "统计信息已清零" << std::endl;

}

}

// 释放资源

cap.release();

cv::destroyAllWindows();

// 停止保存线程并等待结束

frameQueue.stop();

saveThread.join();

// 显示最终统计信息

if (saved_frames_count > 0) {

double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;

std::cout << "最终统计: " << saved_frames_count << " 帧已保存, 平均保存时间: "<< avg_save_time << "ms" << std::endl;

}

return 0;

}

复制代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <string>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <sys/stat.h> // 用于文件夹操作 (C++14兼容)
#include <direct.h>   // Windows下的mkdir

// 线程安全的帧队列
class FrameQueue {
private:
	std::queue<std::pair<cv::Mat, std::string>> queue;
	std::mutex mtx;
	std::condition_variable cond;
	bool stop_flag = false;

public:

	void push(const cv::Mat& frame, const std::string& filename) {
		std::unique_lock<std::mutex> lock(mtx);
		// 深拷贝帧,因为原帧可能会被修改或释放
		queue.push(std::make_pair(frame.clone(), filename));
		cond.notify_one();
	}

	bool pop(std::pair<cv::Mat, std::string>& item) {
		std::unique_lock<std::mutex> lock(mtx);
		cond.wait(lock, [this]() { return !queue.empty() || stop_flag; });

		if (stop_flag && queue.empty()) {
			return false;
		}

		item = queue.front();
		queue.pop();
		return true;
	}

	void stop() {
		std::unique_lock<std::mutex> lock(mtx);
		stop_flag = true;
		cond.notify_all();
	}

	bool empty() {
		std::unique_lock<std::mutex> lock(mtx);
		return queue.empty();
	}
};

// 全局变量
FrameQueue frameQueue;
std::atomic<long long> total_save_time{ 0 };
std::atomic<int> saved_frames_count{ 0 };
std::string imgFolder = "img"; // 图片保存文件夹

// 检查文件夹是否存在 (C++14兼容方法)
bool folderExists(const std::string& folderPath) {
	struct stat info;
	return stat(folderPath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
}

// 创建文件夹 (C++14兼容方法)
bool createFolder(const std::string& folderPath) {
#ifdef _WIN32
	return _mkdir(folderPath.c_str()) == 0;
#else
	return mkdir(folderPath.c_str(), 0733) == 0;
#endif
}

// 检查并创建图片保存文件夹
bool ensureImageFolderExists() {
	// 检查文件夹是否存在
	if (folderExists(imgFolder)) {
		std::cout << "使用图片保存文件夹: " << imgFolder << std::endl;
		return true;
	}

	// 如果不存在,尝试创建文件夹
	if (createFolder(imgFolder)) {
		std::cout << "创建图片保存文件夹: " << imgFolder << std::endl;
		return true;
	}

	std::cerr << "错误: 无法创建图片保存文件夹" << std::endl;
	return false;
}

// 保存图像的线程函数
void saveFrameThread() {
	std::vector<int> compression_params;
	compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
	compression_params.push_back(100);

	while (true) {
		std::pair<cv::Mat, std::string> item;
		if (!frameQueue.pop(item)) {
			break;
		}

		auto start_time = std::chrono::high_resolution_clock::now();

		bool saveResult = cv::imwrite(item.second, item.first);
		//bool saveResult = cv::imwrite(item.second, item.first, compression_params);

		auto end_time = std::chrono::high_resolution_clock::now();
		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

		if (!saveResult) {
			std::cerr << "错误: 无法保存帧: " << item.second << std::endl;
		}
		else {
			long long save_time = duration.count();
			total_save_time += save_time;
			saved_frames_count++;

			std::cout << "帧已保存: " << item.second<< " (保存时间: " << save_time << "ms)" << std::endl;
		}
	}
}

// 保存图像的线程函数
void saveFrameThread2() {
	std::vector<int> compression_params;
	compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
	compression_params.push_back(100);  // 质量

	while (true) {
		std::pair<cv::Mat, std::string> item;
		if (!frameQueue.pop(item)) {
			break;
		}

		auto start_time = std::chrono::high_resolution_clock::now();

		// 编码图像
		std::vector<uchar> buffer;
		auto encode_start = std::chrono::high_resolution_clock::now();
		cv::imencode(".jpg", item.first, buffer, compression_params);
		auto encode_end = std::chrono::high_resolution_clock::now();

		// 写入文件
		auto write_start = std::chrono::high_resolution_clock::now();
		std::ofstream ofs(item.second, std::ios::binary);
		ofs.write(reinterpret_cast<char*>(buffer.data()), buffer.size());
		ofs.close();
		auto write_end = std::chrono::high_resolution_clock::now();

		auto end_time = std::chrono::high_resolution_clock::now();
		auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
		auto encode_duration = std::chrono::duration_cast<std::chrono::milliseconds>(encode_end - encode_start);
		auto write_duration = std::chrono::duration_cast<std::chrono::milliseconds>(write_end - write_start);

		if (!ofs) {
			std::cerr << "错误: 无法保存帧: " << item.second << std::endl;
		}
		else {
			long long total_time = total_duration.count();
			long long encode_time = encode_duration.count();
			long long write_time = write_duration.count();
			total_save_time += total_time;
			saved_frames_count++;

			std::cout << "帧已保存: " << item.second
				<< " (总时间: " << total_time << "ms, 编码: " << encode_time << "ms, 写入: " << write_time << "ms)" << std::endl;
		}
	}
}

// 显示使用说明
void printUsage(const std::string& programName) {
	std::cout << "使用方法: " << programName << " <RTSP_URL>" << std::endl;
	std::cout << "示例: " << programName << " rtsp://username:password@192.168.1.100:554/stream" << std::endl;
	std::cout << "注意: 如果URL中包含特殊字符(如&),请将整个URL用双引号括起来" << std::endl;
	std::cout << std::endl;
	std::cout << "控制命令:" << std::endl;
	std::cout << "  q/Q      - 退出程序" << std::endl;
	std::cout << "  空格键   - 暂停/继续播放" << std::endl;
	std::cout << "  s/S      - 显示保存统计信息" << std::endl;
	std::cout << "  c/C      - 清零统计信息" << std::endl;
}

int main(int argc, char* argv[]) {

	// 检查命令行参数
	if (argc != 2) {
		std::cerr << "错误: 需要提供RTSP URL作为参数" << std::endl;
		printUsage(argv[0]);
		return -1;
	}

	// 从命令行参数获取RTSP URL
	std::string rtspUrl = argv[1];
	std::cout << "使用RTSP URL: " << rtspUrl << std::endl;

	
	// 检查并创建图片保存文件夹
	if (!ensureImageFolderExists()) {
		std::cerr << "错误: 无法创建或访问图片保存文件夹,程序将退出" << std::endl;
		return -1;
	}

	// 打开视频流
	cv::VideoCapture cap(rtspUrl, cv::CAP_FFMPEG);

	if (!cap.isOpened()) {
		std::cerr << "错误: 无法打开RTSP流" << std::endl;
		return -1;
	}

	// 获取视频流的基本信息
	double fps = cap.get(cv::CAP_PROP_FPS);
	int frameWidth = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
	int frameHeight = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
	std::cout << "RTSP流信息: " << frameWidth << "x" << frameHeight << " at " << fps << " FPS" << std::endl;

	// 创建保存线程
	std::thread saveThread(saveFrameThread);

	// 创建窗口
	cv::namedWindow("RTSP Stream", cv::WINDOW_NORMAL);
	cv::resizeWindow("RTSP Stream", frameWidth / 4, frameHeight / 4);

	// 主循环
	cv::Mat frame;
	while (true) {
		// 读取一帧
		if (!cap.read(frame)) {
			std::cerr << "错误: 读取帧失败" << std::endl;
			break;
		}

		// 显示帧
		cv::imshow("RTSP Stream", frame);

		// 获取当前时间(精确到毫秒)
		auto now = std::chrono::system_clock::now();
		auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
		auto epoch = now_ms.time_since_epoch();
		auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
		long long milliseconds = value.count();

		// 将毫秒时间戳转换为可读格式
		std::time_t time_t_now = std::chrono::system_clock::to_time_t(now);
		std::tm tm_struct;
		localtime_s(&tm_struct, &time_t_now);
		std::ostringstream oss;
		oss << std::put_time(&tm_struct, "%Y%m%d_%H%M%S_") << std::setfill('0') << std::setw(3) << (milliseconds % 1000);
		std::string timestampStr = oss.str();

		// 构建保存图像的文件名(包含img文件夹路径)
		std::string filename = imgFolder + "/frame_" + timestampStr + ".bmp";

		// 将帧和文件名添加到队列(由保存线程处理)
		frameQueue.push(frame, filename);

		// 处理键盘输入
		int key = cv::waitKey(1) & 0xFF;
		if (key == 'q' || key == 'Q') {
			break;
		}
		else if (key == ' ') {
			while (true) {
				int innerKey = cv::waitKey(0) & 0xFF;
				if (innerKey == ' ') {
					break;
				}
				else if (innerKey == 'q' || innerKey == 'Q') {
					cv::destroyAllWindows();
					cap.release();
					frameQueue.stop();
					saveThread.join();
					return 0;
				}
			}
		}
		else if (key == 's' || key == 'S') {
			// 显示统计信息
			if (saved_frames_count > 0) {
				double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;
				std::cout << "保存统计: " << saved_frames_count << " 帧, 平均保存时间: "<< avg_save_time << "ms" << std::endl;
			}
		}
		else if (key == 'c' || key == 'C') {
			// 清空统计信息
			total_save_time = 0;
			saved_frames_count = 0;
			std::cout << "统计信息已清零" << std::endl;
		}
	}

	// 释放资源
	cap.release();
	cv::destroyAllWindows();

	// 停止保存线程并等待结束
	frameQueue.stop();
	saveThread.join();

	// 显示最终统计信息
	if (saved_frames_count > 0) {
		double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;
		std::cout << "最终统计: " << saved_frames_count << " 帧已保存, 平均保存时间: "<< avg_save_time << "ms" << std::endl;
	}

	return 0;
}

下载

源码下载

相关推荐
linux开发之路6 小时前
C++ 音视频开发常见面试题及答案汇总
c++·ffmpeg·音视频·流媒体·音视频编解码
小扳6 小时前
SpringBootWeb 篇-深入了解 ThreadLocal 存在内存泄漏问题
java·开发语言·spring boot·面试
CryptoPP6 小时前
跨境金融数据对接实践:印度NSE/BSE股票行情API集成指南
开发语言·后端·金融
再睡一夏就好6 小时前
【C++闯关笔记】STL:list 的学习和使用
c语言·数据结构·c++·笔记·算法·学习笔记
要做朋鱼燕6 小时前
【C++】 list 容器模拟实现解析
开发语言·c++·笔记·职场和发展·list
Ka1Yan6 小时前
MySQL索引优化
开发语言·数据结构·数据库·mysql·算法
MediaTea7 小时前
Python 内置函数:pow()
开发语言·python
AndrewHZ7 小时前
【图像处理基石】图像预处理方面有哪些经典的算法?
图像处理·python·opencv·算法·计算机视觉·cv·图像预处理
闻缺陷则喜何志丹7 小时前
【数论】P10580 [蓝桥杯 2024 国 A] gcd 与 lcm|普及+
c++·数学·蓝桥杯·数论·洛谷