]> git.sesse.net Git - nageru/blobdiff - main.cpp
Allow symlinked frame files. Useful for testing.
[nageru] / main.cpp
index e04af2251240379c4288d4e72f7ab2ea670d42a0..3aebe5a4e05151fe355689a89fe49859219136b7 100644 (file)
--- a/main.cpp
+++ b/main.cpp
@@ -39,6 +39,7 @@ extern "C" {
 #include <QApplication>
 #include <QGLFormat>
 #include <QSurfaceFormat>
+#include <QProgressDialog>
 #include <movit/init.h>
 #include <movit/util.h>
 
@@ -58,7 +59,7 @@ int64_t current_pts = 0;
 
 struct FrameFile {
        FILE *fp = nullptr;
-       int filename_idx;
+       unsigned filename_idx;
        size_t frames_written_so_far = 0;
 };
 std::map<int, FrameFile> open_frame_files;
@@ -69,7 +70,7 @@ vector<string> frame_filenames;  // Under frame_mu.
 
 namespace {
 
-FrameOnDisk write_frame(int stream_idx, int64_t pts, const uint8_t *data, size_t size)
+FrameOnDisk write_frame(int stream_idx, int64_t pts, const uint8_t *data, size_t size, DB *db)
 {
        if (open_frame_files.count(stream_idx) == 0) {
                char filename[256];
@@ -82,16 +83,17 @@ FrameOnDisk write_frame(int stream_idx, int64_t pts, const uint8_t *data, size_t
                }
 
                lock_guard<mutex> lock(frame_mu);
-               int filename_idx = frame_filenames.size();
+               unsigned filename_idx = frame_filenames.size();
                frame_filenames.push_back(filename);
                open_frame_files[stream_idx] = FrameFile{ fp, filename_idx, 0 };
        }
 
        FrameFile &file = open_frame_files[stream_idx];
+       unsigned filename_idx = file.filename_idx;
        string filename;
        {
                lock_guard<mutex> lock(frame_mu);
-               filename = frame_filenames[file.filename_idx];
+               filename = frame_filenames[filename_idx];
        }
 
        FrameHeaderProto hdr;
@@ -126,20 +128,9 @@ FrameOnDisk write_frame(int stream_idx, int64_t pts, const uint8_t *data, size_t
        fflush(file.fp);  // No fsync(), though. We can accept losing a few frames.
        global_disk_space_estimator->report_write(filename, 8 + sizeof(len) + serialized.size() + size, pts);
 
-       if (++file.frames_written_so_far >= 1000) {
-               // Start a new file next time.
-               if (fclose(file.fp) != 0) {
-                       perror("fclose");
-                       exit(1);
-               }
-               open_frame_files.erase(stream_idx);
-
-               // TODO: Write to SQLite.
-       }
-
        FrameOnDisk frame;
        frame.pts = pts;
-       frame.filename_idx = file.filename_idx;
+       frame.filename_idx = filename_idx;
        frame.offset = offset;
        frame.size = size;
 
@@ -149,6 +140,35 @@ FrameOnDisk write_frame(int stream_idx, int64_t pts, const uint8_t *data, size_t
                frames[stream_idx].push_back(frame);
        }
 
+       if (++file.frames_written_so_far >= 1000) {
+               size_t size = ftell(file.fp);
+
+               // Start a new file next time.
+               if (fclose(file.fp) != 0) {
+                       perror("fclose");
+                       exit(1);
+               }
+               open_frame_files.erase(stream_idx);
+
+               // Write information about all frames in the finished file to SQLite.
+               // (If we crash before getting to do this, we'll be scanning through
+               // the file on next startup, and adding it to the database then.)
+               // NOTE: Since we don't fsync(), we could in theory get broken data
+               // but with the right size, but it would seem unlikely.
+               vector<DB::FrameOnDiskAndStreamIdx> frames_this_file;
+               {
+                       lock_guard<mutex> lock(frame_mu);
+                       for (size_t stream_idx = 0; stream_idx < MAX_STREAMS; ++stream_idx) {
+                               for (const FrameOnDisk &frame : frames[stream_idx]) {
+                                       if (frame.filename_idx == filename_idx) {
+                                               frames_this_file.emplace_back(DB::FrameOnDiskAndStreamIdx{ frame, unsigned(stream_idx) });
+                                       }
+                               }
+                       }
+               }
+               db->store_frame_file(filename, size, frames_this_file);
+       }
+
        return frame;
 }
 
@@ -220,6 +240,8 @@ int main(int argc, char **argv)
                // TODO: Delete the surface, too.
        }
 
+       load_existing_frames();
+
        MainWindow main_window;
        main_window.show();
 
@@ -228,7 +250,6 @@ int main(int argc, char **argv)
 
        init_jpeg_vaapi();
 
-       load_existing_frames();
        thread record_thread(record_thread_func);
 
        int ret = app.exec();
@@ -240,9 +261,25 @@ int main(int argc, char **argv)
        return ret;
 }
 
-void load_frame_file(const char *filename, unsigned filename_idx)
+void load_frame_file(const char *filename, const string &basename, unsigned filename_idx, DB *db)
 {
-       // TODO: Look up in the SQLite database.
+       struct stat st;
+       if (stat(filename, &st) == -1) {
+               perror(filename);
+               exit(1);
+       }
+
+       vector<DB::FrameOnDiskAndStreamIdx> all_frames = db->load_frame_file(basename, st.st_size, filename_idx);
+       if (!all_frames.empty()) {
+               // We already had this cached in the database, so no need to look in the file.
+               for (const DB::FrameOnDiskAndStreamIdx &frame : all_frames) {
+                       if (frame.stream_idx >= 0 && frame.stream_idx < MAX_STREAMS) {
+                               frames[frame.stream_idx].push_back(frame.frame);
+                               start_pts = max(start_pts, frame.frame.pts);
+                       }
+               }
+               return;
+       }
 
        FILE *fp = fopen(filename, "rb");
        if (fp == nullptr) {
@@ -310,16 +347,29 @@ void load_frame_file(const char *filename, unsigned filename_idx)
                        frames[hdr.stream_idx()].push_back(frame);
                        start_pts = max(start_pts, hdr.pts());
                }
+               all_frames.emplace_back(DB::FrameOnDiskAndStreamIdx{ frame, unsigned(hdr.stream_idx()) });
        }
 
        if (skipped_bytes > 0) {
                fprintf(stderr, "WARNING: %s: Skipped %zu garbage bytes at the end.\n",
                        filename, skipped_bytes);
        }
+
+       size_t size = ftell(fp);
+       fclose(fp);
+
+       db->store_frame_file(basename, size, all_frames);
 }
 
 void load_existing_frames()
 {
+       QProgressDialog progress("Scanning frame directory...", "Abort", 0, 1);
+       progress.setWindowTitle("Futatabi");
+       progress.setWindowModality(Qt::WindowModal);
+       progress.setMinimumDuration(1000);
+       progress.setMaximum(1);
+       progress.setValue(0);
+
        string frame_dir = global_flags.working_directory + "/frames";
        DIR *dir = opendir(frame_dir.c_str());
        if (dir == nullptr) {
@@ -328,6 +378,7 @@ void load_existing_frames()
                return;
        }
 
+       vector<string> frame_basenames;
        for ( ;; ) {
                errno = 0;
                dirent *de = readdir(dir);
@@ -339,15 +390,35 @@ void load_existing_frames()
                        break;
                }
 
-               if (de->d_type == DT_REG) {
+               if (de->d_type == DT_REG || de->d_type == DT_LNK) {
                        string filename = frame_dir + "/" + de->d_name;
-                       load_frame_file(filename.c_str(), frame_filenames.size());
                        frame_filenames.push_back(filename);
+                       frame_basenames.push_back(de->d_name);
                }
-       }
 
+               if (progress.wasCanceled()) {
+                       exit(1);
+               }
+       }
        closedir(dir);
 
+       progress.setMaximum(frame_filenames.size() + 2);
+       progress.setValue(1);
+
+       progress.setLabelText("Opening database...");
+       DB db(global_flags.working_directory + "/futatabi.db");
+
+       progress.setLabelText("Reading frame files...");
+       progress.setValue(2);
+
+       for (size_t i = 0; i < frame_filenames.size(); ++i) {
+               load_frame_file(frame_filenames[i].c_str(), frame_basenames[i], i, &db);
+               progress.setValue(i + 3);
+               if (progress.wasCanceled()) {
+                       exit(1);
+               }
+       }
+
        if (start_pts == -1) {
                start_pts = 0;
        } else {
@@ -371,6 +442,7 @@ int record_thread_func()
 
        int64_t last_pts = -1;
        int64_t pts_offset;
+       DB db(global_flags.working_directory + "/futatabi.db");
 
        while (!should_quit.load()) {
                AVPacket pkt;
@@ -398,7 +470,7 @@ int record_thread_func()
 
                //fprintf(stderr, "Got a frame from camera %d, pts = %ld, size = %d\n",
                //      pkt.stream_index, pts, pkt.size);
-               FrameOnDisk frame = write_frame(pkt.stream_index, pts, pkt.data, pkt.size);
+               FrameOnDisk frame = write_frame(pkt.stream_index, pts, pkt.data, pkt.size, &db);
 
                post_to_main_thread([pkt, frame] {
                        if (pkt.stream_index == 0) {
@@ -421,33 +493,3 @@ int record_thread_func()
 
        return 0;
 }
-
-string read_frame(FrameOnDisk frame)
-{
-       string filename;
-       {
-               lock_guard<mutex> lock(frame_mu);
-               filename = frame_filenames[frame.filename_idx];
-       }
-
-       // TODO: cache the open file handles
-       FILE *fp = fopen(filename.c_str(), "rb");
-       if (fp == nullptr) {
-               perror(filename.c_str());
-               exit(1);
-       }
-       if (fseek(fp, frame.offset, SEEK_SET) == -1) {
-               perror("fseek");
-               exit(1);
-       }
-
-       string str;
-       str.resize(frame.size);
-       if (fread(&str[0], frame.size, 1, fp) != 1) {
-               perror("fread");
-               exit(1);
-       }
-
-       fclose(fp);
-       return str;
-}