当前位置: 首页 > news >正文

计时器任务实现(保存视频和图像)

      下面是一个简单的计时器任务实现,可持续地每秒保存一幅图像,也可持续地每60秒保存一个视频,图像和视频均以当前时间命名:

      TimerTask类的实现如下:

class TimerTask {
public:
	TimerTask(const std::string& path):path_(path) {}
	~TimerTask() { release(); }
	void release()
	{
		running_ = false;
		if (monitor_thread_.joinable())
			monitor_thread_.join();
	}

	std::tuple<bool, float> set_minimum_available_space(unsigned int gb);
	bool set_save_directory_name(const std::string& dir_name);

	std::string get_local_time();
	std::tuple<bool, std::string> get_current_directory_name();

	void save_video(unsigned int seconds) { save_video_ = true; seconds_ = seconds; }
	void save_image() { save_video_ = false; }

	void monitor_disk_space(unsigned int gb);

private:
	float get_available_space();

	std::string path_;
	unsigned int gb_{0};
	std::string dir_name_{}; // relative path,used to store videos or images
	bool save_video_{false}; // video or image
	unsigned int seconds_{ 0 };
	std::atomic<bool> running_{ true };
	std::thread monitor_thread_;
}; // class TimerTask
namespace {
float get_disk_space(std::string_view path)
{
	namespace fs = std::filesystem;
	constexpr float GB{ 1024.0 * 1024 * 1024 };

	try {
		auto space_info = fs::space(path);
		return (space_info.available / GB);
	} catch (const fs::filesystem_error& e) {
		std::cerr << "Error: " << e.what() << std::endl;
		return 0.f;
	}
}

void monitor_space(unsigned int gb, std::string_view path, std::atomic<bool>& running)
{
	namespace fs = std::filesystem;
	std::mutex mtx;

	if (!fs::exists(path) || !fs::is_directory(path)) {
		std::lock_guard<std::mutex> lock(mtx);
		std::cerr << "Error: " << path << "is not a directory" << std::endl;
	}

	while (running) {
		try {
			float space = get_disk_space(path);
			//std::cout << "space: " << space << ", path: " << path << std::endl;
			if (space < gb) {
				std::vector<fs::path> names;
				for (const auto& entry : fs::directory_iterator(path)) {
					if (fs::is_directory(entry)) {
						names.push_back(entry.path());
					}
				}

				if (names.size() <= 1) {
					//{
					//	std::lock_guard<std::mutex> lock(mtx);
					//	std::cerr << "Error: requires at least 2 directories to exist: " << names.size() << std::endl;
					//}
					continue;
				}

				std::sort(names.begin(), names.end());

				fs::remove_all(names[0]);
				{
					std::lock_guard<std::mutex> lock(mtx);
					std::cout << "delete dir: " << names[0] << std::endl;
				}
			}
		} catch (const fs::filesystem_error& e) {
			std::lock_guard<std::mutex> lock(mtx);
			std::cerr << "Error: " << e.what() << std::endl;
		}

		std::this_thread::sleep_for(std::chrono::seconds(1));
	}
}

} // namespace

float TimerTask::get_available_space()
{
	return get_disk_space(path_);
}

std::tuple<bool, float> TimerTask::set_minimum_available_space(unsigned int gb)
{
	gb_ = gb;

	auto space = get_available_space();
	if (gb_ > space)
		return std::make_tuple(false, space);
	else
		return std::make_tuple(true, space);
}

std::string TimerTask::get_local_time()
{
	using std::chrono::system_clock;
	auto time = system_clock::to_time_t(system_clock::now());
	std::tm* tm = std::localtime(&time);

	std::stringstream buffer;
	buffer << std::put_time(tm, "%Y%m%d%H%M%S");
	return buffer.str();
}

bool TimerTask::set_save_directory_name(const std::string& dir_name)
{
	namespace fs = std::filesystem;
	dir_name_ = dir_name;

	fs::path path(path_ + "/" + dir_name_);
	if (fs::exists(path))
		return true;
	else {
		try {
			return fs::create_directories(path);
		} catch (const fs::filesystem_error& e) {
			std::cerr << "Error: " << e.what() << std::endl;
			return false;
		}
	}
}

std::tuple<bool, std::string> TimerTask::get_current_directory_name()
{
	namespace fs = std::filesystem;
	auto local_time = get_local_time();
	std::string month(local_time.cbegin(), local_time.cbegin() + 6);
	std::string day(local_time.cbegin(), local_time.cbegin() + 8);
	auto curr_dir_name = path_ + "/" + dir_name_ + "/" + month + "/" + day;

	fs::path path(curr_dir_name);
	if (fs::exists(path))
		return std::make_tuple(true, curr_dir_name);
	else {
		try {
			return std::make_tuple(fs::create_directories(path), curr_dir_name);
		} catch (const fs::filesystem_error& e) {
			std::cerr << "Error: " << e.what() << std::endl;
			return std::make_tuple(false, curr_dir_name);
		}
	}
}

void TimerTask::monitor_disk_space(unsigned int gb)
{
	monitor_thread_ = std::thread(monitor_space, gb, path_ + "/" + dir_name_, std::ref(running_));
}

      类主要函数说明:

      (1).set_minimum_available_space函数:用于指定当前可执行文件所在的磁盘剩余空间不低于指定值时才执行;

      (2).set_save_directory_name函数:用于指定生成的图像或视频存在的路径。目录结构形式为:指定目录名/月(202502)/日(20250215)。

      (3).monitor_disk_space函数:线程函数,用于持续监测磁盘剩余空间,低于指定值时则会删除之前已经存储的文件,每次删除一个月份目录。

      (4).get_current_directory_name函数:用于获取当前图像或视频存放的路径名,会按时间自动创建。

      (5).get_local_time函数:用于获取当前时间,格式为如为20250215125015。

      测试代码如下:

int test_write_video()
{
	constexpr unsigned int minimum_available_space{ 100 }; // GB
	constexpr unsigned int video_seconds{ 60 };
	constexpr unsigned int minimum_remaining_space{ 50 }; // GB
	constexpr char dir_name[]{"record"};
	constexpr bool save_video{ true };

	auto current_path = std::filesystem::current_path();
	TimerTask task(current_path.string());
	if (auto [ret, space] = task.set_minimum_available_space(minimum_available_space); !ret) { // GB
		std::cerr << "Error: insufficient remaining space: " << space << "GB" << std::endl;
		return -1;
	}

	if (auto ret = task.set_save_directory_name(dir_name); !ret) {
		std::cerr << "Error: failed to create directory: " << dir_name << std::endl;
		return -1;
	}

	task.monitor_disk_space(minimum_remaining_space);

	cv::VideoCapture cap(0);
	if (!cap.isOpened()) {
		std::cerr << "Error: failed to open capture" << std::endl;
		return -1;
	}

	cv::Mat frame;
	constexpr char win_name[]{"Show"};
	cv::namedWindow(win_name, cv::WINDOW_NORMAL);

	if (save_video) { // video
		task.save_video(video_seconds);

		auto frame_width = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
		auto frame_height = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
		if (frame_width == 0 || frame_height == 0) {
			std::cerr << "Error: failed to get frame width or height: " << frame_width << "," << frame_height << std::endl;
			return -1;
		}

		auto fps = cap.get(cv::CAP_PROP_FPS);
		if (fps <= 0)
			fps = 30.0;

		auto codec = cv::VideoWriter::fourcc('D', 'I', 'V', 'X');
		cv::VideoWriter write_video;
		auto start_time = std::chrono::high_resolution_clock::now();

		while (true) {
			cap >> frame;
			if (frame.empty()) {
				std::cerr << "Error: frame is empty" << std::endl;
				return -1;
			}

			auto current_time = std::chrono::high_resolution_clock::now();
			std::chrono::duration<double> elapsed = current_time - start_time;
			if (elapsed.count() >= video_seconds || !write_video.isOpened()) {
				if (write_video.isOpened())
					write_video.release();

				auto [ret, curr_dir_name] = task.get_current_directory_name();
				if (!ret) {
					std::cerr << "Error: failed to get current directory name: " << curr_dir_name << std::endl;
					return -1;
				}

				auto file_name{ curr_dir_name + "/" + task.get_local_time() + ".avi" };
				write_video.open(file_name, codec, fps, cv::Size(frame_width, frame_height));
				if (!write_video.isOpened()) {
					std::cerr << "Error: failed to open video write: " << file_name << std::endl;
					return -1;
				}

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

			write_video.write(frame);

			cv::imshow(win_name, frame);
			if (cv::waitKey(1) == 27) // Esc exit
				break;
		}

		cap.release();
		if (write_video.isOpened())
			write_video.release();
	} else { // image: save one image per second
		task.save_image();
		auto start_time = std::chrono::high_resolution_clock::now();

		while (true) {
			cap >> frame;
			if (frame.empty()) {
				std::cerr << "Error: frame is empty" << std::endl;
				return -1;
			}

			auto current_time = std::chrono::high_resolution_clock::now();
			std::chrono::duration<double> elapsed = current_time - start_time;
			if (elapsed.count() >= 1.0) {
				auto [ret, curr_dir_name] = task.get_current_directory_name();
				if (!ret) {
					std::cerr << "Error: failed to get current directory name: " << curr_dir_name << std::endl;
					return -1;
				}

				cv::imwrite(curr_dir_name + "/" + task.get_local_time() + ".png", frame);
				start_time = current_time;
			}

			cv::imshow(win_name, frame);
			if (cv::waitKey(1) == 27) // Esc exit
				break;
		}

		cap.release();
	}

	cv::destroyAllWindows();
	task.release();
	return 0;
}

      执行结果如下图所示:

      GitHub:https://github.com/fengbingchun/OpenCV_Test

相关文章:

  • 牛客小白月赛110
  • GGUF格式的DeepSeek-R1-Distill-Qwen-1.5B模型的字段解析
  • 机器学习·最近邻方法(k-NN)
  • 第七天:数据提取-正则表达式
  • 已知自动驾驶的一个场景,如变道,如何做好预期功能安全
  • 空天技术赋能:毫米波基站+高速数字微波构筑应急通信新范式
  • 函数调用过程的详细解析
  • halcon激光三角测量(十七)calibrate_sheet_of_light_3d_calib_object
  • 容器、pod和缓存
  • 快速入门 Tailwind CSS:现代前端开发的利器
  • 【deepseek api 第三方平台使用参考】
  • 日常故障排查 - Java程序故障排查
  • Day19 第六章 二叉树part07
  • ASP.NET Core Web应用(.NET9.0)读取数据库表记录并显示到页面
  • 如何提升爬虫获取数据的准确性?
  • 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-Web.IIS.Administration解读(5)
  • 【MySQL】使用 JDBC 连接数据库
  • GitHub 热点项目介绍
  • 闵氏几何详解
  • 用于仿真得到超材料的S参数后,利用S参数矩阵提取等效介电常数和磁导率
  • 三件珍贵标本开箱!中国恐龙大展5月26日在沪开幕,明星标本汇聚一堂
  • “先增聘再离任”又添一例,景顺长城基金经理鲍无可官宣辞职
  • 国家统计局:2024年城镇单位就业人员工资平稳增长
  • 河南信阳:对违规吃喝问题不遮丑不护短,露头就打、反复敲打
  • 华东政法与复旦上医签署合作框架协议,医学与法学如何交叉融合?
  • 董军同德国国防部长举行会谈