+void load_frame_file(const char *filename, unsigned filename_idx)
+{
+ // TODO: Look up in the SQLite database.
+
+ FILE *fp = fopen(filename, "rb");
+ if (fp == nullptr) {
+ perror(filename);
+ exit(1);
+ }
+
+ size_t magic_offset = 0;
+ size_t skipped_bytes = 0;
+ while (!feof(fp) && !ferror(fp)) {
+ int ch = getc(fp);
+ if (ch == -1) {
+ break;
+ }
+ if (ch != frame_magic[magic_offset++]) {
+ skipped_bytes += magic_offset;
+ magic_offset = 0;
+ continue;
+ }
+ if (magic_offset < frame_magic_len) {
+ // Still reading the magic (hopefully).
+ continue;
+ }
+
+ // OK, found the magic. Try to parse the frame header.
+ magic_offset = 0;
+
+ if (skipped_bytes > 0) {
+ fprintf(stderr, "WARNING: %s: Skipped %zu garbage bytes in the middle.\n",
+ filename, skipped_bytes);
+ skipped_bytes = 0;
+ }
+
+ uint32_t len;
+ if (fread(&len, sizeof(len), 1, fp) != 1) {
+ fprintf(stderr, "WARNING: %s: Short read when getting length.\n", filename);
+ break;
+ }
+
+ string serialized;
+ serialized.resize(ntohl(len));
+ if (fread(&serialized[0], serialized.size(), 1, fp) != 1) {
+ fprintf(stderr, "WARNING: %s: Short read when reading frame header (%zu bytes).\n", filename, serialized.size());
+ break;
+ }
+
+ FrameHeaderProto hdr;
+ if (!hdr.ParseFromString(serialized)) {
+ fprintf(stderr, "WARNING: %s: Corrupted frame header.\n", filename);
+ continue;
+ }
+
+ FrameOnDisk frame;
+ frame.pts = hdr.pts();
+ frame.offset = ftell(fp);
+ frame.filename_idx = filename_idx;
+ frame.size = hdr.file_size();
+
+ if (fseek(fp, frame.offset + frame.size, SEEK_SET) == -1) {
+ fprintf(stderr, "WARNING: %s: Could not seek past frame (probably truncated).\n", filename);
+ continue;
+ }
+
+ if (hdr.stream_idx() >= 0 && hdr.stream_idx() < MAX_STREAMS) {
+ frames[hdr.stream_idx()].push_back(frame);
+ start_pts = max(start_pts, hdr.pts());
+ }
+ }
+
+ if (skipped_bytes > 0) {
+ fprintf(stderr, "WARNING: %s: Skipped %zu garbage bytes at the end.\n",
+ filename, skipped_bytes);
+ }
+}
+