下面是一个简单的计时器任务实现,可持续地每秒保存一幅图像,也可持续地每60秒保存一个视频,图像和视频均以当前时间命名:
TimerTask类的实现如下:
cpp
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
cpp
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。
测试代码如下:
cpp
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;
}
执行结果如下图所示:
