#include <QApplication>
#include <QGLFormat>
#include <QSurfaceFormat>
+#include <QProgressDialog>
#include <movit/init.h>
#include <movit/util.h>
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;
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];
}
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;
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;
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) });
+ }
+ }
+ }
+ }
+
+ const char *basename = filename.c_str();
+ while (strchr(basename, '/') != nullptr) {
+ basename = strchr(basename, '/');
+ }
+ db->store_frame_file(basename, size, frames_this_file);
+ }
+
return frame;
}
// TODO: Delete the surface, too.
}
+ load_existing_frames();
+
MainWindow main_window;
main_window.show();
init_jpeg_vaapi();
- load_existing_frames();
thread record_thread(record_thread_func);
int ret = app.exec();
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) {
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) {
return;
}
+ vector<string> frame_basenames;
for ( ;; ) {
errno = 0;
dirent *de = readdir(dir);
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 {
int64_t last_pts = -1;
int64_t pts_offset;
+ DB db(global_flags.working_directory + "/futatabi.db");
while (!should_quit.load()) {
AVPacket pkt;
//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) {
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;
-}