From cfe82eb664cda969e745449dee37bac2e723d609 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 2 Oct 2018 00:50:28 +0200 Subject: [PATCH] Add a disk space estimator. Code largely borrowed from Nageru. --- Makefile | 2 +- disk_space_estimator.cpp | 58 ++++++++++++++++++++++++++++++++++++++++ disk_space_estimator.h | 51 +++++++++++++++++++++++++++++++++++ main.cpp | 3 +++ mainwindow.cpp | 41 ++++++++++++++++++++++++++++ mainwindow.h | 5 ++++ ui_mainwindow.ui | 22 +++++++++++++++ 7 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 disk_space_estimator.cpp create mode 100644 disk_space_estimator.h diff --git a/Makefile b/Makefile index 9e234ca..11634ba 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ OBJS += $(OBJS_WITH_MOC:.o=.moc.o) OBJS += flow.o gpu_timers.o OBJS += ffmpeg_raii.o main.o player.o httpd.o mux.o metacube2.o video_stream.o context.o chroma_subsampler.o -OBJS += vaapi_jpeg_decoder.o memcpy_interleaved.o db.o +OBJS += vaapi_jpeg_decoder.o memcpy_interleaved.o db.o disk_space_estimator.o OBJS += state.pb.o %.o: %.cpp diff --git a/disk_space_estimator.cpp b/disk_space_estimator.cpp new file mode 100644 index 0000000..50460f9 --- /dev/null +++ b/disk_space_estimator.cpp @@ -0,0 +1,58 @@ +#include "disk_space_estimator.h" + +#include +#include +#include +#include + +#include "timebase.h" + +DiskSpaceEstimator::DiskSpaceEstimator(DiskSpaceEstimator::callback_t callback) + : callback(callback) +{ +} + +void DiskSpaceEstimator::report_write(const std::string &filename, uint64_t pts) +{ + // Reject points that are out-of-order (happens with B-frames). + if (!measure_points.empty() && pts <= measure_points.back().pts) { + return; + } + + // Remove too old points. + while (measure_points.size() > 1 && measure_points.front().pts + window_length < pts) { + measure_points.pop_front(); + } + + struct stat st; + if (stat(filename.c_str(), &st) == -1) { + perror(filename.c_str()); + return; + } + + total_size += st.st_size; + + struct statfs fst; + if (statfs(filename.c_str(), &fst) == -1) { + perror(filename.c_str()); + return; + } + + off_t free_bytes = off_t(fst.f_bavail) * fst.f_frsize; + + if (!measure_points.empty()) { + double bytes_per_second = double(total_size - measure_points.front().size) / + (pts - measure_points.front().pts) * TIMEBASE; + double seconds_left = free_bytes / bytes_per_second; + + // Only report every second, since updating the UI can be expensive. + if (last_pts_reported == 0 || pts - last_pts_reported >= TIMEBASE) { + callback(free_bytes, seconds_left); + last_pts_reported = pts; + } + } + + measure_points.push_back({ pts, total_size }); +} + +DiskSpaceEstimator *global_disk_space_estimator = nullptr; // Created in MainWindow::MainWindow(). diff --git a/disk_space_estimator.h b/disk_space_estimator.h new file mode 100644 index 0000000..c7f1df5 --- /dev/null +++ b/disk_space_estimator.h @@ -0,0 +1,51 @@ +#ifndef _DISK_SPACE_ESTIMATOR_H +#define _DISK_SPACE_ESTIMATOR_H + +// A class responsible for measuring how much disk there is left when we +// store our video to disk, and how much recording time that equates to. +// It gets callbacks from the Mux writing the stream to disk (which also +// knows which filesystem the file is going to), makes its calculations, +// and calls back to the MainWindow, which shows it to the user. +// +// The bitrate is measured over a simple 30-second sliding window. + +#include +#include +#include +#include +#include + +#include "timebase.h" + +class DiskSpaceEstimator +{ +public: + typedef std::function callback_t; + DiskSpaceEstimator(callback_t callback); + + // Report that a video frame with the given pts has just been written + // to the given file, so the estimator should stat the file and see + // by big it is. (The file is assumed to hold only that single frame, + // unlike in Nageru, where it is a growing file.) + // + // If the filename changed since last time, the estimation is reset. + // is taken to be in TIMEBASE units (see timebase.h). + void report_write(const std::string &filename, uint64_t pts); + +private: + static constexpr int64_t window_length = 30 * TIMEBASE; + + callback_t callback; + + struct MeasurePoint { + uint64_t pts; + off_t size; + }; + std::deque measure_points; + uint64_t last_pts_reported = 0; + off_t total_size = 0; +}; + +extern DiskSpaceEstimator *global_disk_space_estimator; + +#endif // !defined(_DISK_SPACE_ESTIMATOR_H) diff --git a/main.cpp b/main.cpp index dc80bc2..b7ae8f0 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,7 @@ extern "C" { #include "clip_list.h" #include "context.h" #include "defs.h" +#include "disk_space_estimator.h" #include "mainwindow.h" #include "ffmpeg_raii.h" #include "httpd.h" @@ -206,6 +207,8 @@ int record_thread_func() fwrite(pkt.data, pkt.size, 1, fp); fclose(fp); + global_disk_space_estimator->report_write(filename, pts); + post_to_main_thread([pkt, pts] { if (pkt.stream_index == 0) { global_mainwindow->ui->input1_display->setFrame(pkt.stream_index, pts, /*interpolated=*/false); diff --git a/mainwindow.cpp b/mainwindow.cpp index cfc9769..9d09582 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,6 +1,7 @@ #include "mainwindow.h" #include "clip_list.h" +#include "disk_space_estimator.h" #include "player.h" #include "post_to_main_thread.h" #include "timebase.h" @@ -17,6 +18,7 @@ #include using namespace std; +using namespace std::placeholders; MainWindow *global_mainwindow = nullptr; ClipList *cliplist_clips; @@ -33,6 +35,11 @@ MainWindow::MainWindow() global_mainwindow = this; ui->setupUi(this); + global_disk_space_estimator = new DiskSpaceEstimator(bind(&MainWindow::report_disk_space, this, _1, _2)); + disk_free_label = new QLabel(this); + disk_free_label->setStyleSheet("QLabel {padding-right: 5px;}"); + ui->menuBar->setCornerWidget(disk_free_label); + StateProto state = db.get_state(); cliplist_clips = new ClipList(state.clip_list()); @@ -590,3 +597,37 @@ void MainWindow::playlist_selection_changed() any_selected && selected->selectedRows().back().row() < int(playlist_clips->size()) - 1); ui->play_btn->setEnabled(!playlist_clips->empty()); } + +void MainWindow::report_disk_space(off_t free_bytes, double estimated_seconds_left) +{ + char time_str[256]; + if (estimated_seconds_left < 60.0) { + strcpy(time_str, "Less than a minute"); + } else if (estimated_seconds_left < 1800.0) { // Less than half an hour: Xm Ys (red). + int s = lrintf(estimated_seconds_left); + int m = s / 60; + s %= 60; + snprintf(time_str, sizeof(time_str), "%dm %ds", m, s); + } else if (estimated_seconds_left < 3600.0) { // Less than an hour: Xm. + int m = lrintf(estimated_seconds_left / 60.0); + snprintf(time_str, sizeof(time_str), "%dm", m); + } else if (estimated_seconds_left < 36000.0) { // Less than ten hours: Xh Ym. + int m = lrintf(estimated_seconds_left / 60.0); + int h = m / 60; + m %= 60; + snprintf(time_str, sizeof(time_str), "%dh %dm", h, m); + } else { // More than ten hours: Xh. + int h = lrintf(estimated_seconds_left / 3600.0); + snprintf(time_str, sizeof(time_str), "%dh", h); + } + char buf[256]; + snprintf(buf, sizeof(buf), "Disk free: %'.0f MB (approx. %s)", free_bytes / 1048576.0, time_str); + + std::string label = buf; + + post_to_main_thread([this, label]{ + disk_free_label->setText(QString::fromStdString(label)); + ui->menuBar->setCornerWidget(disk_free_label); // Need to set this again for the sizing to get right. + }); +} + diff --git a/mainwindow.h b/mainwindow.h index 3621d32..679feb9 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -3,6 +3,8 @@ #include #include + +#include #include #include "db.h" @@ -25,6 +27,7 @@ public: Ui::MainWindow *ui; private: + QLabel *disk_free_label; Player *preview_player, *live_player; DB db; @@ -83,6 +86,8 @@ private: void resizeEvent(QResizeEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; + void report_disk_space(off_t free_bytes, double estimated_seconds_left); + private slots: void relayout(); }; diff --git a/ui_mainwindow.ui b/ui_mainwindow.ui index 27986aa..5b0021a 100644 --- a/ui_mainwindow.ui +++ b/ui_mainwindow.ui @@ -315,6 +315,28 @@ + + + + 0 + 0 + 1038 + 22 + + + + + &File + + + + + + + + Quit + + -- 2.39.2