]> git.sesse.net Git - nageru/commitdiff
Add a disk space estimator. Code largely borrowed from Nageru.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 1 Oct 2018 22:50:28 +0000 (00:50 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 1 Oct 2018 22:50:28 +0000 (00:50 +0200)
Makefile
disk_space_estimator.cpp [new file with mode: 0644]
disk_space_estimator.h [new file with mode: 0644]
main.cpp
mainwindow.cpp
mainwindow.h
ui_mainwindow.ui

index 9e234caf4dac38e2046f47d75e63ed9d87c1053d..11634ba1ff53c89bf2d831d2aee8eac312a7d31d 100644 (file)
--- 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 (file)
index 0000000..50460f9
--- /dev/null
@@ -0,0 +1,58 @@
+#include "disk_space_estimator.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <memory>
+
+#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 (file)
index 0000000..c7f1df5
--- /dev/null
@@ -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 <stdint.h>
+#include <sys/types.h>
+#include <deque>
+#include <functional>
+#include <string>
+
+#include "timebase.h"
+
+class DiskSpaceEstimator
+{
+public:
+       typedef std::function<void(off_t free_bytes, double estimated_seconds_left)> 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.
+       // <pts> 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<MeasurePoint> 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)
index dc80bc2edcc3e6ff24efc2033344ca149998ad23..b7ae8f0b9be54d9b9beff73c75379ca7234ba6e6 100644 (file)
--- 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);
index cfc976965bfb4988efb82bec69cb4978c94666ba..9d0958208d2c742bc123d7f76ccf839b2244bda1 100644 (file)
@@ -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 <sqlite3.h>
 
 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, "<font color=\"red\">Less than a minute</font>");
+       } 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), "<font color=\"red\">%dm %ds</font>", 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.
+                       });
+}
+
index 3621d32ce65f8467ad99d3941f78a7d2e3c16672..679feb9bdf1873378650e49beee5e055e9acf5eb 100644 (file)
@@ -3,6 +3,8 @@
 
 #include <stdbool.h>
 #include <sys/types.h>
+
+#include <QLabel>
 #include <QMainWindow>
 
 #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();
 };
index 27986aae8c07a5e8a7f0bf18c4773200ef968bfd..5b0021aa6f14b4e7220d3a8b1f75d49677907595 100644 (file)
     </item>
    </layout>
   </widget>
+  <widget class="QMenuBar" name="menuBar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>1038</width>
+     <height>22</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>&amp;File</string>
+    </property>
+    <addaction name="actionQuit"/>
+   </widget>
+   <addaction name="menuFile"/>
+  </widget>
+  <action name="actionQuit">
+   <property name="text">
+    <string>Quit</string>
+   </property>
+  </action>
  </widget>
  <customwidgets>
   <customwidget>