5 #include <condition_variable>
14 #include <sys/types.h>
19 #include <libavformat/avformat.h>
22 #include "clip_list.h"
25 #include "disk_space_estimator.h"
26 #include "ffmpeg_raii.h"
28 #include "frame_on_disk.h"
31 #include "mainwindow.h"
33 #include "post_to_main_thread.h"
34 #include "ref_counted_gl_sync.h"
36 #include "ui_mainwindow.h"
37 #include "vaapi_jpeg_decoder.h"
39 #include <QApplication>
41 #include <QSurfaceFormat>
42 #include <movit/init.h>
43 #include <movit/util.h>
46 using namespace std::chrono;
48 constexpr char frame_magic[] = "Ftbifrm0";
49 constexpr size_t frame_magic_len = 8;
51 mutex RefCountedGLsync::fence_lock;
52 atomic<bool> should_quit{false};
54 int64_t start_pts = -1;
56 // TODO: Replace by some sort of GUI control, I guess.
57 int64_t current_pts = 0;
62 size_t frames_written_so_far = 0;
64 std::map<int, FrameFile> open_frame_files;
67 vector<FrameOnDisk> frames[MAX_STREAMS]; // Under frame_mu.
68 vector<string> frame_filenames; // Under frame_mu.
72 FrameOnDisk write_frame(int stream_idx, int64_t pts, const uint8_t *data, size_t size)
74 if (open_frame_files.count(stream_idx) == 0) {
76 snprintf(filename, sizeof(filename), "%s/frames/cam%d-pts%09ld.frames",
77 global_flags.working_directory.c_str(), stream_idx, pts);
78 FILE *fp = fopen(filename, "wb");
84 lock_guard<mutex> lock(frame_mu);
85 int filename_idx = frame_filenames.size();
86 frame_filenames.push_back(filename);
87 open_frame_files[stream_idx] = FrameFile{ fp, filename_idx, 0 };
90 FrameFile &file = open_frame_files[stream_idx];
93 lock_guard<mutex> lock(frame_mu);
94 filename = frame_filenames[file.filename_idx];
98 hdr.set_stream_idx(stream_idx);
100 hdr.set_file_size(size);
103 if (!hdr.SerializeToString(&serialized)) {
104 fprintf(stderr, "Frame header serialization failed.\n");
107 uint32_t len = htonl(serialized.size());
109 if (fwrite(frame_magic, frame_magic_len, 1, file.fp) != 1) {
113 if (fwrite(&len, sizeof(len), 1, file.fp) != 1) {
117 if (fwrite(serialized.data(), serialized.size(), 1, file.fp) != 1) {
121 off_t offset = ftell(file.fp);
122 if (fwrite(data, size, 1, file.fp) != 1) {
126 fflush(file.fp); // No fsync(), though. We can accept losing a few frames.
127 global_disk_space_estimator->report_write(filename, 8 + sizeof(len) + serialized.size() + size, pts);
129 if (++file.frames_written_so_far >= 1000) {
130 // Start a new file next time.
131 if (fclose(file.fp) != 0) {
135 open_frame_files.erase(stream_idx);
137 // TODO: Write to SQLite.
142 frame.filename_idx = file.filename_idx;
143 frame.offset = offset;
147 lock_guard<mutex> lock(frame_mu);
148 assert(stream_idx < MAX_STREAMS);
149 frames[stream_idx].push_back(frame);
159 void load_existing_frames();
160 int record_thread_func();
162 int main(int argc, char **argv)
164 parse_flags(argc, argv);
165 if (optind == argc) {
166 global_flags.stream_source = "multiangle.mp4";
167 global_flags.slow_down_input = true;
168 } else if (optind + 1 == argc) {
169 global_flags.stream_source = argv[optind];
175 string frame_dir = global_flags.working_directory + "/frames";
178 if (stat(frame_dir.c_str(), &st) == -1) {
179 fprintf(stderr, "%s does not exist, creating it.\n", frame_dir.c_str());
180 if (mkdir(frame_dir.c_str(), 0777) == -1) {
181 perror(global_flags.working_directory.c_str());
186 avformat_network_init();
187 global_httpd = new HTTPD;
189 QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
192 fmt.setDepthBufferSize(0);
193 fmt.setStencilBufferSize(0);
194 fmt.setProfile(QSurfaceFormat::CoreProfile);
195 fmt.setMajorVersion(4);
196 fmt.setMinorVersion(5);
198 // Turn off vsync, since Qt generally gives us at most frame rate
199 // (display frequency) / (number of QGLWidgets active).
200 fmt.setSwapInterval(0);
202 QSurfaceFormat::setDefaultFormat(fmt);
204 QGLFormat::setDefaultFormat(QGLFormat::fromSurfaceFormat(fmt));
206 QApplication app(argc, argv);
207 global_share_widget = new QGLWidget();
208 if (!global_share_widget->isValid()) {
209 fprintf(stderr, "Failed to initialize OpenGL. Futatabi needs at least OpenGL 4.5 to function properly.\n");
215 QSurface *surface = create_surface();
216 QOpenGLContext *context = create_context(surface);
217 make_current(context, surface);
218 CHECK(movit::init_movit(MOVIT_SHADER_DIR, movit::MOVIT_DEBUG_OFF));
219 delete_context(context);
220 // TODO: Delete the surface, too.
223 MainWindow main_window;
226 global_httpd->add_endpoint("/queue_status", bind(&MainWindow::get_queue_status, &main_window), HTTPD::NO_CORS_POLICY);
227 global_httpd->start(global_flags.http_port);
231 load_existing_frames();
232 thread record_thread(record_thread_func);
234 int ret = app.exec();
237 record_thread.join();
238 JPEGFrameView::shutdown();
243 void load_frame_file(const char *filename, unsigned filename_idx)
245 // TODO: Look up in the SQLite database.
247 FILE *fp = fopen(filename, "rb");
253 size_t magic_offset = 0;
254 size_t skipped_bytes = 0;
255 while (!feof(fp) && !ferror(fp)) {
260 if (ch != frame_magic[magic_offset++]) {
261 skipped_bytes += magic_offset;
265 if (magic_offset < frame_magic_len) {
266 // Still reading the magic (hopefully).
270 // OK, found the magic. Try to parse the frame header.
273 if (skipped_bytes > 0) {
274 fprintf(stderr, "WARNING: %s: Skipped %zu garbage bytes in the middle.\n",
275 filename, skipped_bytes);
280 if (fread(&len, sizeof(len), 1, fp) != 1) {
281 fprintf(stderr, "WARNING: %s: Short read when getting length.\n", filename);
286 serialized.resize(ntohl(len));
287 if (fread(&serialized[0], serialized.size(), 1, fp) != 1) {
288 fprintf(stderr, "WARNING: %s: Short read when reading frame header (%zu bytes).\n", filename, serialized.size());
292 FrameHeaderProto hdr;
293 if (!hdr.ParseFromString(serialized)) {
294 fprintf(stderr, "WARNING: %s: Corrupted frame header.\n", filename);
299 frame.pts = hdr.pts();
300 frame.offset = ftell(fp);
301 frame.filename_idx = filename_idx;
302 frame.size = hdr.file_size();
304 if (fseek(fp, frame.offset + frame.size, SEEK_SET) == -1) {
305 fprintf(stderr, "WARNING: %s: Could not seek past frame (probably truncated).\n", filename);
309 if (hdr.stream_idx() >= 0 && hdr.stream_idx() < MAX_STREAMS) {
310 frames[hdr.stream_idx()].push_back(frame);
311 start_pts = max(start_pts, hdr.pts());
315 if (skipped_bytes > 0) {
316 fprintf(stderr, "WARNING: %s: Skipped %zu garbage bytes at the end.\n",
317 filename, skipped_bytes);
321 void load_existing_frames()
323 string frame_dir = global_flags.working_directory + "/frames";
324 DIR *dir = opendir(frame_dir.c_str());
325 if (dir == nullptr) {
333 dirent *de = readdir(dir);
342 if (de->d_type == DT_REG) {
343 string filename = frame_dir + "/" + de->d_name;
344 load_frame_file(filename.c_str(), frame_filenames.size());
345 frame_filenames.push_back(filename);
351 if (start_pts == -1) {
354 // Add a gap of one second from the old frames to the new ones.
355 start_pts += TIMEBASE;
358 for (int stream_idx = 0; stream_idx < MAX_STREAMS; ++stream_idx) {
359 sort(frames[stream_idx].begin(), frames[stream_idx].end(),
360 [](const auto &a, const auto &b) { return a.pts < b.pts; });
364 int record_thread_func()
366 auto format_ctx = avformat_open_input_unique(global_flags.stream_source.c_str(), nullptr, nullptr);
367 if (format_ctx == nullptr) {
368 fprintf(stderr, "%s: Error opening file\n", global_flags.stream_source.c_str());
372 int64_t last_pts = -1;
375 while (!should_quit.load()) {
377 unique_ptr<AVPacket, decltype(av_packet_unref)*> pkt_cleanup(
378 &pkt, av_packet_unref);
379 av_init_packet(&pkt);
383 // TODO: Make it possible to abort av_read_frame() (use an interrupt callback);
384 // right now, should_quit will be ignored if it's hung on I/O.
385 if (av_read_frame(format_ctx.get(), &pkt) != 0) {
389 // Convert pts to our own timebase.
390 AVRational stream_timebase = format_ctx->streams[pkt.stream_index]->time_base;
391 int64_t pts = av_rescale_q(pkt.pts, stream_timebase, AVRational{ 1, TIMEBASE });
393 // Translate offset into our stream.
394 if (last_pts == -1) {
395 pts_offset = start_pts - pts;
397 pts = std::max(pts + pts_offset, start_pts);
399 //fprintf(stderr, "Got a frame from camera %d, pts = %ld, size = %d\n",
400 // pkt.stream_index, pts, pkt.size);
401 FrameOnDisk frame = write_frame(pkt.stream_index, pts, pkt.data, pkt.size);
403 post_to_main_thread([pkt, frame] {
404 if (pkt.stream_index == 0) {
405 global_mainwindow->ui->input1_display->setFrame(pkt.stream_index, frame);
406 } else if (pkt.stream_index == 1) {
407 global_mainwindow->ui->input2_display->setFrame(pkt.stream_index, frame);
408 } else if (pkt.stream_index == 2) {
409 global_mainwindow->ui->input3_display->setFrame(pkt.stream_index, frame);
410 } else if (pkt.stream_index == 3) {
411 global_mainwindow->ui->input4_display->setFrame(pkt.stream_index, frame);
415 if (last_pts != -1 && global_flags.slow_down_input) {
416 this_thread::sleep_for(microseconds((pts - last_pts) * 1000000 / TIMEBASE));