]> git.sesse.net Git - nageru/commitdiff
Make the UI show free disk space, and a rough estimation of for how much longer we...
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 28 Sep 2016 21:56:23 +0000 (23:56 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Wed, 28 Sep 2016 21:57:58 +0000 (23:57 +0200)
12 files changed:
Makefile
disk_space_estimator.cpp [new file with mode: 0644]
disk_space_estimator.h [new file with mode: 0644]
mainwindow.cpp
mainwindow.h
mixer.cpp
mux.cpp
mux.h
quicksync_encoder.cpp
quicksync_encoder.h
video_encoder.cpp
video_encoder.h

index 6d2063e74c13bda60a9ae676405c8960e258d2b6..5e9029dbe513c75eb46031ed30d7637dd0f823f0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o correlation
 OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o correlation_meter.moc.o aboutdialog.moc.o
 
 # Mixer objects
-OBJS += mixer.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampling_queue.o httpd.o ebu_r128_proc.o flags.o image_input.o stereocompressor.o filter.o alsa_output.o correlation_measurer.o
+OBJS += mixer.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampling_queue.o httpd.o ebu_r128_proc.o flags.o image_input.o stereocompressor.o filter.o alsa_output.o correlation_measurer.o disk_space_estimator.o
 
 # Streaming and encoding objects
 OBJS += quicksync_encoder.o x264_encoder.o x264_speed_control.o video_encoder.o metacube2.o mux.o audio_encoder.o ffmpeg_raii.o
diff --git a/disk_space_estimator.cpp b/disk_space_estimator.cpp
new file mode 100644 (file)
index 0000000..1cb7d6f
--- /dev/null
@@ -0,0 +1,54 @@
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include "disk_space_estimator.h"
+#include "timebase.h"
+
+DiskSpaceEstimator::DiskSpaceEstimator(DiskSpaceEstimator::callback_t callback)
+       : callback(callback)
+{
+}
+
+void DiskSpaceEstimator::report_write(const std::string &filename, uint64_t pts)
+{
+       if (filename != last_filename) {
+               last_filename = filename;
+               measure_points.clear();
+       }
+
+       // 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;
+       }
+
+       struct statfs fst;
+       if (statfs(filename.c_str(), &fst) == -1) {
+               perror(filename.c_str());
+               return;
+       }
+
+       if (!measure_points.empty()) {
+               off_t free_bytes = off_t(fst.f_bavail) * fst.f_frsize;
+               double bytes_per_second = double(st.st_size - measure_points.front().size) /
+                       (pts - measure_points.front().pts) * TIMEBASE;
+               double seconds_left = free_bytes / bytes_per_second;
+               callback(free_bytes, seconds_left);
+       }
+
+       measure_points.push_back({ pts, st.st_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..b585bc3
--- /dev/null
@@ -0,0 +1,50 @@
+#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 <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 how much it grew since last time. Called by the Mux object
+       // responsible for writing to the stream on disk.
+       //
+       // 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;
+       std::string last_filename;
+
+       struct MeasurePoint {
+               uint64_t pts;
+               off_t size;
+       };
+       std::deque<MeasurePoint> measure_points;
+};
+
+extern DiskSpaceEstimator *global_disk_space_estimator;
+
+#endif  // !defined(_DISK_SPACE_ESTIMATOR_H)
index fec26f22fbf19544e4cf9dc921a3a002a696dbe6..841952f8b28eda6b2c83483aedf717e00ef6c834 100644 (file)
@@ -19,6 +19,7 @@
 #include <QString>
 
 #include "aboutdialog.h"
+#include "disk_space_estimator.h"
 #include "flags.h"
 #include "glwidget.h"
 #include "lrameter.h"
@@ -95,6 +96,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);
+
        ui->me_live->set_output(Mixer::OUTPUT_LIVE);
        ui->me_preview->set_output(Mixer::OUTPUT_PREVIEW);
 
@@ -287,6 +293,39 @@ void MainWindow::cutoff_knob_changed(int value)
        ui->locut_cutoff_display->setText(buf);
 }
 
+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.
+       });
+}
+
 void MainWindow::limiter_threshold_knob_changed(int value)
 {
        float threshold_dbfs = value * 0.1f;
index 31a204857910beb3f734b834a73e7053ee3745db..89c5e13cd46252da4e69f0c50c5f3d28c8914858 100644 (file)
@@ -17,6 +17,7 @@ class Display;
 class MainWindow;
 }  // namespace Ui
 
+class QLabel;
 class QPushButton;
 
 class MainWindow : public QMainWindow
@@ -55,11 +56,15 @@ private:
        bool eventFilter(QObject *watched, QEvent *event) override;
        void set_white_balance(int channel_number, int x, int y);
 
+       // Called from DiskSpaceEstimator.
+       void report_disk_space(off_t free_bytes, double estimated_seconds_left);
+
        // Called from the mixer.
        void audio_level_callback(float level_lufs, float peak_db, float global_level_lufs, float range_low_lufs, float range_high_lufs, float gain_staging_db, float final_makeup_gain_db, float correlation);
        std::chrono::steady_clock::time_point last_audio_level_callback;
 
        Ui::MainWindow *ui;
+       QLabel *disk_free_label;
        QPushButton *transition_btn1, *transition_btn2, *transition_btn3;
        std::vector<Ui::Display *> previews;
        int current_wb_pick_display = -1;
index f8ad584e9ac6996e83e7595fd6891d03705478e1..1a4b2cf141ab1562e9eae438a3bc458ded7af476 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -37,6 +37,7 @@
 #include "context.h"
 #include "decklink_capture.h"
 #include "defs.h"
+#include "disk_space_estimator.h"
 #include "flags.h"
 #include "video_encoder.h"
 #include "pbo_frame_allocator.h"
@@ -165,7 +166,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        display_chain->set_dither_bits(0);  // Don't bother.
        display_chain->finalize();
 
-       video_encoder.reset(new VideoEncoder(resource_pool.get(), h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd));
+       video_encoder.reset(new VideoEncoder(resource_pool.get(), h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd, global_disk_space_estimator));
 
        // Start listening for clients only once VideoEncoder has written its header, if any.
        httpd.start(9095);
diff --git a/mux.cpp b/mux.cpp
index df974f0644520aa7c31ffb757abe1b733af2e480..4f9f4d04a04d9a7221fbe696784c5b43f3158fbc 100644 (file)
--- a/mux.cpp
+++ b/mux.cpp
@@ -29,8 +29,8 @@ struct PacketBefore {
        const AVFormatContext * const ctx;
 };
 
-Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base)
-       : avctx(avctx)
+Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base, std::function<void(int64_t)> write_callback)
+       : avctx(avctx), write_callback(write_callback)
 {
        avstream_video = avformat_new_stream(avctx, nullptr);
        if (avstream_video == nullptr) {
@@ -130,6 +130,14 @@ void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
        }
 
        av_packet_unref(&pkt_copy);
+
+       // Note: This will be wrong in the case of plugged packets, but that only happens
+       // for network streams, not for files, and write callbacks are only really relevant
+       // for files. (We don't want to do this from write_packet_or_die, as it only has
+       // the rescaled pts, which is unsuitable for callback.)
+       if (pkt.stream_index == 0 && write_callback != nullptr) {
+               write_callback(pts);
+       }
 }
 
 void Mux::write_packet_or_die(const AVPacket &pkt)
diff --git a/mux.h b/mux.h
index b60b02a88585c470b9c934d1ea8b0f69f3ebb13c..3f5007db9f75c6040415be1a01ce6ccd74059db0 100644 (file)
--- a/mux.h
+++ b/mux.h
@@ -9,6 +9,7 @@ extern "C" {
 #include <libavformat/avio.h>
 }
 
+#include <functional>
 #include <mutex>
 #include <queue>
 #include <vector>
@@ -20,8 +21,10 @@ public:
                CODEC_NV12,  // Uncompressed 4:2:0.
        };
 
-       // Takes ownership of avctx.
-       Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const std::string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base);
+       // Takes ownership of avctx. <write_callback> will be called every time
+       // a write has been made to the video stream (id 0), with the pts of
+       // the just-written frame. (write_callback can be nullptr.)
+       Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const std::string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base, std::function<void(int64_t)> write_callback);
        ~Mux();
        void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts);
 
@@ -45,6 +48,8 @@ private:
        std::vector<AVPacket *> plugged_packets;  // Protected by <mu>.
 
        AVStream *avstream_video, *avstream_audio;
+
+       std::function<void(int64_t)> write_callback;
 };
 
 #endif  // !defined(_MUX_H)
index cc4020f69508ffe9d5706be7a88be8b7cc240da9..4e974223d15def72a9d348a2a4eb94039077d0e2 100644 (file)
@@ -21,6 +21,7 @@
 #include <condition_variable>
 #include <cstddef>
 #include <cstdint>
+#include <functional>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -42,6 +43,7 @@ extern "C" {
 #include "audio_encoder.h"
 #include "context.h"
 #include "defs.h"
+#include "disk_space_estimator.h"
 #include "ffmpeg_raii.h"
 #include "flags.h"
 #include "mux.h"
@@ -50,6 +52,7 @@ extern "C" {
 #include "x264_encoder.h"
 
 using namespace std;
+using namespace std::placeholders;
 
 class QOpenGLContext;
 class QSurface;
@@ -206,7 +209,7 @@ FrameReorderer::Frame FrameReorderer::get_first_frame()
 
 class QuickSyncEncoderImpl {
 public:
-       QuickSyncEncoderImpl(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder);
+       QuickSyncEncoderImpl(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator);
        ~QuickSyncEncoderImpl();
        void add_audio(int64_t pts, vector<float> audio);
        bool begin_frame(GLuint *y_tex, GLuint *cbcr_tex);
@@ -355,6 +358,8 @@ private:
        int frame_height;
        int frame_width_mbaligned;
        int frame_height_mbaligned;
+
+       DiskSpaceEstimator *disk_space_estimator;
 };
 
 // Supposedly vaRenderPicture() is supposed to destroy the buffer implicitly,
@@ -1728,8 +1733,8 @@ int QuickSyncEncoderImpl::deinit_va()
     return 0;
 }
 
-QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder)
-       : current_storage_frame(0), resource_pool(resource_pool), surface(surface), x264_encoder(x264_encoder), frame_width(width), frame_height(height)
+QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator)
+       : current_storage_frame(0), resource_pool(resource_pool), surface(surface), x264_encoder(x264_encoder), frame_width(width), frame_height(height), disk_space_estimator(disk_space_estimator)
 {
        file_audio_encoder.reset(new AudioEncoder(AUDIO_OUTPUT_CODEC_NAME, DEFAULT_AUDIO_OUTPUT_BIT_RATE, oformat));
        open_output_file(filename);
@@ -1951,7 +1956,8 @@ void QuickSyncEncoderImpl::open_output_file(const std::string &filename)
 
        string video_extradata = "";  // FIXME: See other comment about global headers.
        AVCodecParametersWithDeleter audio_codecpar = file_audio_encoder->get_codec_parameters();
-       file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, video_extradata, audio_codecpar.get(), TIMEBASE));
+       file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, video_extradata, audio_codecpar.get(), TIMEBASE,
+               std::bind(&DiskSpaceEstimator::report_write, disk_space_estimator, filename, _1)));
 }
 
 void QuickSyncEncoderImpl::encode_thread_func()
@@ -2156,8 +2162,8 @@ void QuickSyncEncoderImpl::encode_frame(QuickSyncEncoderImpl::PendingFrame frame
 }
 
 // Proxy object.
-QuickSyncEncoder::QuickSyncEncoder(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder)
-       : impl(new QuickSyncEncoderImpl(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder)) {}
+QuickSyncEncoder::QuickSyncEncoder(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator)
+       : impl(new QuickSyncEncoderImpl(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder, disk_space_estimator)) {}
 
 // Must be defined here because unique_ptr<> destructor needs to know the impl.
 QuickSyncEncoder::~QuickSyncEncoder() {}
index c1e844bdab05332c10262a4a0def1890148f626a..16fd3d179cd048a2d6b29e447d3209a673ecebca 100644 (file)
@@ -30,6 +30,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <atomic>
+#include <functional>
 #include <memory>
 #include <string>
 #include <vector>
@@ -42,6 +43,7 @@ extern "C" {
 #include "ref_counted_gl_sync.h"
 
 class AudioEncoder;
+class DiskSpaceEstimator;
 class Mux;
 class QSurface;
 class QuickSyncEncoderImpl;
@@ -57,7 +59,7 @@ class ResourcePool;
 // .cpp file.
 class QuickSyncEncoder {
 public:
-        QuickSyncEncoder(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder);
+        QuickSyncEncoder(const std::string &filename, movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, AVOutputFormat *oformat, X264Encoder *x264_encoder, DiskSpaceEstimator *disk_space_estimator);
         ~QuickSyncEncoder();
 
        void set_stream_mux(Mux *mux);  // Does not take ownership. Must be called unless x264 is used for the stream.
index 0f3785f198279bcd1580c7e66d98738e1f295add..dfbf565437d5dcfc311f84c7245eaa06eaee68be 100644 (file)
@@ -35,8 +35,8 @@ string generate_local_dump_filename(int frame)
 
 }  // namespace
 
-VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, HTTPD *httpd)
-       : resource_pool(resource_pool), surface(surface), va_display(va_display), width(width), height(height), httpd(httpd)
+VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, HTTPD *httpd, DiskSpaceEstimator *disk_space_estimator)
+       : resource_pool(resource_pool), surface(surface), va_display(va_display), width(width), height(height), httpd(httpd), disk_space_estimator(disk_space_estimator)
 {
        oformat = av_guess_format(global_flags.stream_mux_name.c_str(), nullptr, nullptr);
        assert(oformat != nullptr);
@@ -50,7 +50,7 @@ VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const
        }
 
        string filename = generate_local_dump_filename(/*frame=*/0);
-       quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get()));
+       quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get(), disk_space_estimator));
 
        open_output_stream();
        stream_audio_encoder->add_mux(stream_mux.get());
@@ -92,7 +92,7 @@ void VideoEncoder::do_cut(int frame)
                qs_needing_cleanup.emplace_back(old_encoder);
        }).detach();
 
-       quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get()));
+       quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, x264_encoder.get(), disk_space_estimator));
        quicksync_encoder->set_stream_mux(stream_mux.get());
 }
 
@@ -146,7 +146,8 @@ void VideoEncoder::open_output_stream()
        }
 
        int time_base = global_flags.stream_coarse_timebase ? COARSE_TIMEBASE : TIMEBASE;
-       stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(), time_base));
+       stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(), time_base,
+               /*write_callback=*/nullptr));
 }
 
 int VideoEncoder::write_packet2_thunk(void *opaque, uint8_t *buf, int buf_size, AVIODataMarkerType type, int64_t time)
index fd16ac82f66584cc1f5a76eb783bf954b3a056d9..9a9f861839e1834f3b2228c492527731928f3242 100644 (file)
@@ -16,6 +16,7 @@
 #include "ref_counted_frame.h"
 #include "ref_counted_gl_sync.h"
 
+class DiskSpaceEstimator;
 class HTTPD;
 class QSurface;
 class QuickSyncEncoder;
@@ -27,7 +28,7 @@ class ResourcePool;
 
 class VideoEncoder {
 public:
-       VideoEncoder(movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, HTTPD *httpd);
+       VideoEncoder(movit::ResourcePool *resource_pool, QSurface *surface, const std::string &va_display, int width, int height, HTTPD *httpd, DiskSpaceEstimator *disk_space_estimator);
        ~VideoEncoder();
 
        void add_audio(int64_t pts, std::vector<float> audio);
@@ -52,6 +53,7 @@ private:
        std::string va_display;
        int width, height;
        HTTPD *httpd;
+       DiskSpaceEstimator *disk_space_estimator;
 
        std::unique_ptr<Mux> stream_mux;  // To HTTP.
        std::unique_ptr<AudioEncoder> stream_audio_encoder;