Ctrl K
ComunidadGuías…

src/zm_mp4_sidx.cpp

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
 * This file is part of the ZoneMinder Project. See AUTHORS file for contributors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "zm_mp4_sidx.h"

#include "zm_logger.h"

#include <algorithm>
#include <cerrno>
#include <cmath>
#include <cstring>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
}

namespace zm_mp4 {

namespace {

constexpr int64_t kSidxHeaderBytes = 40;      // version 1, up to the references
constexpr int64_t kSidxReferenceBytes = 12;
constexpr size_t kMaxReferenceCount = 65535;  // reference_count is 16 bits
constexpr int64_t kMaxReferencedSize = (int64_t(1) << 31) - 1;
constexpr uint32_t kMaxSamplesPerTrun = 1u << 20;   // sanity, not a spec limit

// tfhd flags, 14496-12 8.8.7
constexpr uint32_t kTfhdBaseDataOffset = 0x000001;
constexpr uint32_t kTfhdSampleDescriptionIndex = 0x000002;
constexpr uint32_t kTfhdDefaultSampleDuration = 0x000008;
constexpr uint32_t kTfhdDefaultSampleSize = 0x000010;
constexpr uint32_t kTfhdDefaultSampleFlags = 0x000020;

// trun flags, 14496-12 8.8.8
constexpr uint32_t kTrunDataOffset = 0x000001;
constexpr uint32_t kTrunFirstSampleFlags = 0x000004;
constexpr uint32_t kTrunSampleDuration = 0x000100;
constexpr uint32_t kTrunSampleSize = 0x000200;
constexpr uint32_t kTrunSampleFlags = 0x000400;
constexpr uint32_t kTrunSampleCts = 0x000800;

// sample_is_non_sync_sample in sample flags, 14496-12 8.8.3.1
constexpr uint32_t kSampleIsNonSync = 0x00010000;

uint32_t rb32(const uint8_t *p) {
  return (static_cast<uint32_t>(p[0]) << 24) | (static_cast<uint32_t>(p[1]) << 16)
       | (static_cast<uint32_t>(p[2]) << 8) | static_cast<uint32_t>(p[3]);
}

uint64_t rb64(const uint8_t *p) {
  return (static_cast<uint64_t>(rb32(p)) << 32) | rb32(p + 4);
}

void wb16(uint8_t *p, uint16_t v) {
  p[0] = static_cast<uint8_t>(v >> 8);
  p[1] = static_cast<uint8_t>(v);
}

void wb32(uint8_t *p, uint32_t v) {
  p[0] = static_cast<uint8_t>(v >> 24);
  p[1] = static_cast<uint8_t>(v >> 16);
  p[2] = static_cast<uint8_t>(v >> 8);
  p[3] = static_cast<uint8_t>(v);
}

void wb64(uint8_t *p, uint64_t v) {
  wb32(p, static_cast<uint32_t>(v >> 32));
  wb32(p + 4, static_cast<uint32_t>(v));
}

bool read_exact(int fd, void *buffer, size_t bytes, int64_t offset) {
  uint8_t *p = static_cast<uint8_t *>(buffer);
  while (bytes > 0) {
    ssize_t got = pread(fd, p, bytes, offset);
    if (got <= 0) return false;
    p += got;
    bytes -= static_cast<size_t>(got);
    offset += got;
  }
  return true;
}

bool write_exact(int fd, const void *buffer, size_t bytes, int64_t offset) {
  const uint8_t *p = static_cast<const uint8_t *>(buffer);
  while (bytes > 0) {
    ssize_t put = pwrite(fd, p, bytes, offset);
    if (put <= 0) return false;
    p += put;
    bytes -= static_cast<size_t>(put);
    offset += put;
  }
  return true;
}

// Put [offset, offset + bytes) on disk. The rest of the event is still in the
// page cache when finalize() runs, and an fsync would write all of it -- often
// hundreds of MB -- just to order a 64 KiB write before an 8-byte one. Linux
// can write back the range alone; it does not flush the drive's own cache,
// which is the ordering the kernel itself gives data=ordered writes. Elsewhere,
// or where the filesystem refuses, fall back to the whole file; fsync rather
// than fdatasync, which macOS does not declare.
bool sync_range(int fd, int64_t offset, int64_t bytes) {
#if defined(__linux__)
  if (sync_file_range(fd, offset, bytes, SYNC_FILE_RANGE_WAIT_BEFORE
                      | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER) == 0) {
    return true;
  }
  if (errno != EINVAL && errno != ENOSYS && errno != ESPIPE) return false;
#endif
  return fsync(fd) == 0;
}

struct Box {
  char type[5] = {0};
  int64_t offset = 0;
  int64_t size = 0;
  int64_t header = 0;

  int64_t body() const { return offset + header; }
  int64_t end() const { return offset + size; }
  bool is(const char *what) const { return memcmp(type, what, 4) == 0; }
};

// One box at `offset`. Fails on anything that would run past `limit`, which
// is how a truncated recording is refused rather than half-indexed.
bool read_box(int fd, int64_t offset, int64_t limit, Box *box) {
  if (offset < 0 || offset + 8 > limit) return false;
  uint8_t head[16];
  if (!read_exact(fd, head, 8, offset)) return false;
  int64_t size = rb32(head);
  int64_t header = 8;
  if (size == 1) {
    if (offset + 16 > limit) return false;
    if (!read_exact(fd, head + 8, 8, offset + 8)) return false;
    size = static_cast<int64_t>(rb64(head + 8));
    header = 16;
  } else if (size == 0) {
    size = limit - offset;
  }
  if (size < header || offset > limit - size) return false;
  memcpy(box->type, head + 4, 4);
  box->type[4] = 0;
  box->offset = offset;
  box->size = size;
  box->header = header;
  return true;
}

// The first child of [start, end) with this type.
bool find_box(int fd, int64_t start, int64_t end, const char *type, Box *box) {
  int64_t pos = start;
  while (pos + 8 <= end) {
    Box candidate;
    if (!read_box(fd, pos, end, &candidate)) return false;
    if (candidate.is(type)) {
      *box = candidate;
      return true;
    }
    pos = candidate.end();
  }
  return false;
}

}  // namespace

int64_t media_end(int fd, int64_t file_size) {
  // An `mfro` is always the last 16 bytes of an `mfra` and states its size,
  // so the trailer can be found without walking the fragments to reach it.
  if (file_size < 16) return file_size;
  uint8_t tail[16];
  if (!read_exact(fd, tail, 16, file_size - 16)) return file_size;
  if (rb32(tail) != 16 || memcmp(tail + 4, "mfro", 4) != 0) return file_size;
  int64_t mfra_size = rb32(tail + 12);
  if (mfra_size <= 0 || mfra_size > file_size) return file_size;
  int64_t offset = file_size - mfra_size;
  Box mfra;
  if (!read_box(fd, offset, file_size, &mfra) || !mfra.is("mfra")
      || mfra.size != mfra_size) {
    return file_size;
  }
  return offset;
}

bool read_video_track(int fd, int64_t file_size, VideoTrack *track) {
  Box moov;
  if (!find_box(fd, 0, file_size, "moov", &moov)) {
    Debug(1, "sidx: no moov");
    return false;
  }

  bool found = false;
  int64_t pos = moov.body();
  while (pos + 8 <= moov.end()) {
    Box trak;
    if (!read_box(fd, pos, moov.end(), &trak)) return false;
    pos = trak.end();
    if (!trak.is("trak")) continue;

    Box tkhd;
    Box mdia;
    if (!find_box(fd, trak.body(), trak.end(), "tkhd", &tkhd)) continue;
    if (!find_box(fd, trak.body(), trak.end(), "mdia", &mdia)) continue;

    Box hdlr;
    Box mdhd;
    if (!find_box(fd, mdia.body(), mdia.end(), "hdlr", &hdlr)) continue;
    if (!find_box(fd, mdia.body(), mdia.end(), "mdhd", &mdhd)) continue;

    uint8_t handler[4];
    if (!read_exact(fd, handler, 4, hdlr.body() + 8)) return false;
    if (memcmp(handler, "vide", 4) != 0) continue;

    uint8_t version = 0;
    if (!read_exact(fd, &version, 1, tkhd.body())) return false;
    uint8_t word[4];
    if (!read_exact(fd, word, 4, tkhd.body() + (version == 1 ? 20 : 12))) return false;
    track->id = rb32(word);

    if (!read_exact(fd, &version, 1, mdhd.body())) return false;
    if (!read_exact(fd, word, 4, mdhd.body() + (version == 1 ? 20 : 12))) return false;
    track->timescale = rb32(word);
    found = true;
    break;
  }
  if (!found) {
    Debug(1, "sidx: no video track in moov");
    return false;
  }
  if (track->timescale == 0) {
    Debug(1, "sidx: video track %u has a zero timescale", track->id);
    return false;
  }

  Box mvex;
  if (find_box(fd, moov.body(), moov.end(), "mvex", &mvex)) {
    int64_t at = mvex.body();
    while (at + 8 <= mvex.end()) {
      Box trex;
      if (!read_box(fd, at, mvex.end(), &trex)) break;
      at = trex.end();
      if (!trex.is("trex")) continue;
      uint8_t body[20];
      if (!read_exact(fd, body, 20, trex.body() + 4)) break;
      if (rb32(body) == track->id) {
        track->default_sample_duration = rb32(body + 8);
        track->default_sample_flags = rb32(body + 16);
        break;
      }
    }
  }
  return true;
}

namespace {

// One `traf`: which track it belongs to, and -- for the video track -- the
// decode time it starts at, how long its samples last and whether the first
// of them is a sync sample.
bool parse_traf(int fd, const Box &traf, const VideoTrack &track,
                bool want_first_cts, uint32_t *track_id,
                int64_t *tfdt, int64_t *duration, int64_t *first_cts,
                bool *first_is_sync) {
  Box tfhd;
  if (!find_box(fd, traf.body(), traf.end(), "tfhd", &tfhd)) return false;
  uint8_t head[8];
  if (!read_exact(fd, head, 8, tfhd.body())) return false;
  const uint32_t flags = rb32(head) & 0xFFFFFF;
  *track_id = rb32(head + 4);

  int64_t at = tfhd.body() + 8;
  if (flags & kTfhdBaseDataOffset) at += 8;
  if (flags & kTfhdSampleDescriptionIndex) at += 4;
  uint32_t default_duration = track.default_sample_duration;
  if (flags & kTfhdDefaultSampleDuration) {
    uint8_t word[4];
    if (!read_exact(fd, word, 4, at)) return false;
    default_duration = rb32(word);
    at += 4;
  }
  if (flags & kTfhdDefaultSampleSize) at += 4;
  uint32_t default_flags = track.default_sample_flags;
  if (flags & kTfhdDefaultSampleFlags) {
    uint8_t word[4];
    if (!read_exact(fd, word, 4, at)) return false;
    default_flags = rb32(word);
  }

  *tfdt = -1;
  Box tfdt_box;
  if (find_box(fd, traf.body(), traf.end(), "tfdt", &tfdt_box)) {
    uint8_t version = 0;
    if (!read_exact(fd, &version, 1, tfdt_box.body())) return false;
    uint8_t value[8];
    if (version == 1) {
      if (!read_exact(fd, value, 8, tfdt_box.body() + 4)) return false;
      *tfdt = static_cast<int64_t>(rb64(value));
    } else {
      if (!read_exact(fd, value, 4, tfdt_box.body() + 4)) return false;
      *tfdt = rb32(value);
    }
  }

  *duration = 0;
  *first_cts = 0;
  *first_is_sync = false;
  bool have_first = false;
  bool have_first_flags = false;
  int64_t pos = traf.body();
  while (pos + 8 <= traf.end()) {
    Box trun;
    if (!read_box(fd, pos, traf.end(), &trun)) return false;
    pos = trun.end();
    if (!trun.is("trun")) continue;

    uint8_t trun_head[8];
    if (!read_exact(fd, trun_head, 8, trun.body())) return false;
    const uint8_t version = trun_head[0];
    const uint32_t trun_flags = rb32(trun_head) & 0xFFFFFF;
    const uint32_t count = rb32(trun_head + 4);
    if (count > kMaxSamplesPerTrun) return false;

    int64_t entry = trun.body() + 8;
    if (trun_flags & kTrunDataOffset) entry += 4;
    // The first sample's flags: its own field, else the table's, else the
    // defaults. A trun of no samples leaves the question to the next one.
    if (!have_first_flags && count > 0) {
      uint32_t sample_flags = default_flags;
      if (trun_flags & kTrunFirstSampleFlags) {
        uint8_t word[4];
        if (entry + 4 > trun.end() || !read_exact(fd, word, 4, entry)) return false;
        sample_flags = rb32(word);
      } else if (trun_flags & kTrunSampleFlags) {
        const int64_t field = entry
            + ((trun_flags & kTrunSampleDuration) ? 4 : 0)
            + ((trun_flags & kTrunSampleSize) ? 4 : 0);
        uint8_t word[4];
        if (field + 4 > trun.end() || !read_exact(fd, word, 4, field)) return false;
        sample_flags = rb32(word);
      }
      *first_is_sync = !(sample_flags & kSampleIsNonSync);
      have_first_flags = true;
    }
    if (trun_flags & kTrunFirstSampleFlags) entry += 4;
    const int per_sample =
        ((trun_flags & kTrunSampleDuration) ? 4 : 0)
      + ((trun_flags & kTrunSampleSize) ? 4 : 0)
      + ((trun_flags & kTrunSampleFlags) ? 4 : 0)
      + ((trun_flags & kTrunSampleCts) ? 4 : 0);

    if (!(trun_flags & kTrunSampleDuration)) {
      // Every sample lasts the default, so the table need not be read.
      *duration += static_cast<int64_t>(count) * default_duration;
    }
    if (per_sample > 0 && count > 0) {
      const size_t bytes = static_cast<size_t>(count) * static_cast<size_t>(per_sample);
      if (entry + static_cast<int64_t>(bytes) > trun.end()) return false;
      std::vector<uint8_t> table(bytes);
      if (!read_exact(fd, table.data(), bytes, entry)) return false;
      for (uint32_t i = 0; i < count; i++) {
        const uint8_t *sample = table.data() + static_cast<size_t>(i) * per_sample;
        if (trun_flags & kTrunSampleDuration) *duration += rb32(sample);
        if (want_first_cts && !have_first && (trun_flags & kTrunSampleCts)) {
          const uint8_t *cts = sample + per_sample - 4;
          // 14496-12 8.8.8: signed in version 1, unsigned in version 0.
          *first_cts = (version == 1)
              ? static_cast<int64_t>(static_cast<int32_t>(rb32(cts)))
              : static_cast<int64_t>(rb32(cts));
          have_first = true;
        }
      }
    }
  }
  return true;
}

}  // namespace

bool scan_fragments(int fd, int64_t from, int64_t to, const VideoTrack &track,
                    std::vector<Fragment> *fragments) {
  fragments->clear();
  int64_t pos = from;
  while (pos + 8 <= to) {
    Box moof;
    if (!read_box(fd, pos, to, &moof) || !moof.is("moof")) {
      Debug(1, "sidx: expected a moof at %" PRId64, pos);
      return false;
    }
    Box mdat;
    if (!read_box(fd, moof.end(), to, &mdat) || !mdat.is("mdat")) {
      Debug(1, "sidx: moof at %" PRId64 " is not followed by an mdat", pos);
      return false;
    }

    Fragment fragment;
    fragment.offset = moof.offset;
    fragment.size = moof.size + mdat.size;
    bool have_video = false;

    int64_t at = moof.body();
    while (at + 8 <= moof.end()) {
      Box traf;
      if (!read_box(fd, at, moof.end(), &traf)) return false;
      at = traf.end();
      if (!traf.is("traf")) continue;

      uint32_t track_id = 0;
      int64_t tfdt = -1;
      int64_t duration = 0;
      int64_t first_cts = 0;
      bool first_is_sync = false;
      const bool want_cts = fragments->empty();
      if (!parse_traf(fd, traf, track, want_cts, &track_id, &tfdt, &duration, &first_cts,
                      &first_is_sync)) {
        Debug(1, "sidx: cannot parse a traf at %" PRId64, traf.offset);
        return false;
      }
      if (track_id != track.id) continue;
      if (tfdt < 0) {
        Debug(1, "sidx: the video traf at %" PRId64 " has no tfdt", traf.offset);
        return false;
      }
      fragment.tfdt = tfdt;
      fragment.trun_duration = duration;
      fragment.first_cts = want_cts ? first_cts : 0;
      fragment.starts_with_sap = first_is_sync;
      have_video = true;
    }
    if (!have_video) {
      Debug(1, "sidx: the moof at %" PRId64 " has no video traf", moof.offset);
      return false;
    }
    fragments->push_back(fragment);
    pos = mdat.end();
  }
  if (pos != to) {
    Debug(1, "sidx: %" PRId64 " bytes at %" PRId64 " are not a fragment", to - pos, pos);
    return false;
  }
  return !fragments->empty();
}

bool fragments_follow_header(AVFormatContext *oc) {
  void *muxer = oc ? oc->priv_data : nullptr;
  int64_t flags = 0;
  // movflags belongs to the MOV/MP4 muxer alone. Matroska and WebM take the
  // same encoder options and ignore it, and a `free` box in an EBML stream
  // would corrupt it.
  if (!muxer || !av_opt_find(muxer, "movflags", nullptr, 0, 0) ||
      av_opt_get_int(muxer, "movflags", 0, &flags) < 0) {
    return false;
  }
  // A flag this libavformat does not know is never set.
  auto has = [muxer, flags](const char *name) {
    const AVOption *flag = av_opt_find(muxer, name, "movflags", 0, 0);
    return flag && (flags & flag->default_val.i64);
  };
  // empty_moov writes the moov in the header, and fragmenting follows from it.
  // Without it -- frag_keyframe alone, say -- or with delay_moov, the moov
  // comes with the first fragment, after the region, where no index can end
  // at the first moof.
  if (!has("empty_moov") || has("delay_moov")) return false;
  // global_sidx shifts the fragments to put an index of its own in front of
  // them; hybrid_fragmented turns the file into a non-fragmented one.
  if (has("global_sidx") || has("hybrid_fragmented")) return false;
  // One index covers bare moof+mdat pairs: dash writes a sidx before every
  // fragment and write_prft a prft.
  if (has("dash") && !has("skip_sidx")) return false;
  int64_t prft = 0;
  if (av_opt_get_int(muxer, "write_prft", 0, &prft) >= 0 && prft > 0) return false;
  return true;
}

int64_t reserve_region(AVFormatContext *oc, int64_t reserve) {
  if (!oc || !oc->pb || reserve < 8 || reserve > INT32_MAX) return -1;
  if (!fragments_follow_header(oc)) {
    Debug(1, "sidx: %s output with these movflags cannot take a leading index",
          oc->oformat ? oc->oformat->name : "unknown");
    return -1;
  }
  const int64_t offset = avio_tell(oc->pb);
  std::vector<uint8_t> region(static_cast<size_t>(reserve), 0);
  wb32(region.data(), static_cast<uint32_t>(reserve));
  memcpy(region.data() + 4, "free", 4);
  avio_write(oc->pb, region.data(), static_cast<int>(region.size()));
  avio_flush(oc->pb);
  if (avio_tell(oc->pb) != offset + reserve) {
    Warning("sidx: reserved %" PRId64 " bytes at %" PRId64 " but the file grew to %" PRId64
            "; not indexing it", reserve, offset, avio_tell(oc->pb));
    return -1;
  }
  Debug(1, "sidx: reserved %" PRId64 " bytes for a leading index at %" PRId64, reserve, offset);
  return offset;
}

int64_t reserve_size(double seconds, double fragment_seconds) {
  if (!(seconds > 0) || !(fragment_seconds > 0)) return kSidxReserve;
  const double references = 2 * std::ceil(seconds / fragment_seconds);
  // Room for a whole `free` box header as well, so any count up to this fits.
  const double bytes = kSidxHeaderBytes + 8 + kSidxReferenceBytes * references;
  if (!(bytes < kSidxReserve)) return kSidxReserve;
  const int64_t blocks = (static_cast<int64_t>(bytes) + kSidxMinReserve - 1) / kSidxMinReserve;
  return std::max(blocks * kSidxMinReserve, kSidxMinReserve);
}

size_t max_references(int64_t reserve) {
  // A region is either filled exactly, or leaves room for a whole `free` box
  // header (8 bytes) to cover what is left.
  const int64_t exact = reserve - kSidxHeaderBytes;
  if (exact >= 0 && exact % kSidxReferenceBytes == 0) {
    const int64_t n = exact / kSidxReferenceBytes;
    if (n >= 0 && static_cast<uint64_t>(n) <= kMaxReferenceCount) {
      return static_cast<size_t>(n);
    }
  }
  const int64_t n = (reserve - kSidxHeaderBytes - 8) / kSidxReferenceBytes;
  if (n <= 0) return 0;
  return static_cast<size_t>(n) > kMaxReferenceCount
      ? kMaxReferenceCount : static_cast<size_t>(n);
}

std::vector<uint8_t> build_sidx_region(const VideoTrack &track,
                                       const std::vector<Fragment> &fragments,
                                       int64_t reserve) {
  std::vector<uint8_t> empty;
  if (fragments.empty() || track.timescale == 0) return empty;

  const size_t limit = max_references(reserve);
  if (limit == 0) {
    Debug(1, "sidx: a %" PRId64 " byte region holds no references", reserve);
    return empty;
  }

  // More fragments than references: fold neighbours together. A merged
  // reference still points at a real `moof`, so the demuxer seeks to it and
  // reads forward; merging coarsens seeking and never breaks playback.
  const size_t per_reference =
      fragments.size() <= limit ? 1 : (fragments.size() + limit - 1) / limit;

  struct Reference {
    int64_t size;
    int64_t duration;
    bool starts_with_sap;   // a merged reference starts where its first fragment does
  };
  std::vector<Reference> references;
  references.reserve((fragments.size() + per_reference - 1) / per_reference);

  for (size_t i = 0; i < fragments.size(); i += per_reference) {
    Reference reference{0, 0, fragments[i].starts_with_sap};
    for (size_t j = i; j < i + per_reference && j < fragments.size(); j++) {
      reference.size += fragments[j].size;
      // A fragment lasts until the next one starts, which its `tfdt` states
      // exactly; only the last has to be summed from its `trun`.
      reference.duration += (j + 1 < fragments.size())
          ? fragments[j + 1].tfdt - fragments[j].tfdt
          : fragments[j].trun_duration;
    }
    if (reference.size <= 0 || reference.size > kMaxReferencedSize) {
      Debug(1, "sidx: a reference would cover %" PRId64 " bytes", reference.size);
      return empty;
    }
    // An event that closes on a keyframe ends in a fragment of that frame
    // alone, and the muxer gives the last sample the packet's duration,
    // often 0. Fold it into the reference before rather than refuse the file.
    if (reference.duration == 0 && i + per_reference >= fragments.size()
        && !references.empty()
        && references.back().size + reference.size <= kMaxReferencedSize) {
      references.back().size += reference.size;
      break;
    }
    if (reference.duration <= 0 || reference.duration > 0xFFFFFFFF) {
      Debug(1, "sidx: a reference would last %" PRId64 " ticks", reference.duration);
      return empty;
    }
    references.push_back(reference);
  }

  const int64_t sidx_bytes =
      kSidxHeaderBytes + kSidxReferenceBytes * static_cast<int64_t>(references.size());
  const int64_t padding = reserve - sidx_bytes;
  if (padding < 0 || (padding > 0 && padding < 8)) {
    Debug(1, "sidx: %" PRId64 " bytes of sidx do not fit a %" PRId64 " byte region",
          sidx_bytes, reserve);
    return empty;
  }

  int64_t earliest = fragments.front().tfdt + fragments.front().first_cts;
  if (earliest < 0) earliest = 0;

  std::vector<uint8_t> region(static_cast<size_t>(reserve), 0);
  uint8_t *p = region.data();
  if (padding > 0) {
    // The padding goes FIRST, so the sidx ends where the fragments begin and
    // first_offset can be 0 -- which is what makes the index complete.
    wb32(p, static_cast<uint32_t>(padding));
    memcpy(p + 4, "free", 4);
    p += padding;
  }
  wb32(p, static_cast<uint32_t>(sidx_bytes));
  memcpy(p + 4, "sidx", 4);
  p[8] = 1;                       // version 1: 64-bit times and offsets
  p[9] = p[10] = p[11] = 0;       // flags
  wb32(p + 12, track.id);
  wb32(p + 16, track.timescale);
  wb64(p + 20, static_cast<uint64_t>(earliest));
  wb64(p + 28, 0);                // first_offset: the next byte is the moof
  wb16(p + 36, 0);                // reserved
  wb16(p + 38, static_cast<uint16_t>(references.size()));
  uint8_t *entry = p + 40;
  for (const Reference &reference : references) {
    wb32(entry, static_cast<uint32_t>(reference.size));      // type 0 + size
    wb32(entry + 4, static_cast<uint32_t>(reference.duration));
    // starts_with_SAP and SAP type 1 for a fragment that opens on a sync
    // sample; otherwise none, as a fragment cut mid-GOP (frag_duration) has.
    wb32(entry + 8, reference.starts_with_sap ? 0x90000000 : 0);
    entry += kSidxReferenceBytes;
  }
  return region;
}

bool write_leading_sidx(const std::string &path, int64_t region_offset,
                        int64_t region_size) {
  if (region_offset < 0 || region_size <= 0) return false;

  const int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
  if (fd < 0) {
    Warning("sidx: cannot reopen %s: %s", path.c_str(), strerror(errno));
    return false;
  }

  bool done = false;
  do {
    struct stat info;
    if (fstat(fd, &info) != 0) {
      Warning("sidx: cannot stat %s: %s", path.c_str(), strerror(errno));
      break;
    }
    const int64_t file_size = info.st_size;
    const int64_t first_moof = region_offset + region_size;

    Box moof;
    if (!read_box(fd, first_moof, file_size, &moof) || !moof.is("moof")) {
      Debug(1, "sidx: no moof at %" PRId64 " in %s, leaving the region alone",
            first_moof, path.c_str());
      break;
    }

    VideoTrack track;
    if (!read_video_track(fd, file_size, &track)) break;

    std::vector<Fragment> fragments;
    const int64_t end = media_end(fd, file_size);
    if (!scan_fragments(fd, first_moof, end, track, &fragments)) break;

    const std::vector<uint8_t> region = build_sidx_region(track, fragments, region_size);
    if (region.empty()) break;

    // The region is one `free` box until the moment its first eight bytes
    // change, so write the body first and that header last: a write cut short
    // anywhere leaves a file that still parses exactly as it did before. The
    // body must reach the disk before the header: a header without its body
    // would leave zeros where the sidx should be, a box that runs to EOF. The
    // header needs no sync of its own -- if it is lost, the region is still
    // the `free` box it was.
    if (!write_exact(fd, region.data() + 8, region.size() - 8, region_offset + 8)) {
      Warning("sidx: cannot write the index into %s: %s", path.c_str(), strerror(errno));
      break;
    }
    if (!sync_range(fd, region_offset + 8, region_size - 8)) {
      Warning("sidx: cannot flush %s: %s", path.c_str(), strerror(errno));
      break;
    }
    if (!write_exact(fd, region.data(), 8, region_offset)) {
      Warning("sidx: cannot commit the index in %s: %s", path.c_str(), strerror(errno));
      break;
    }

    Debug(1, "sidx: indexed %s -- %zu fragments in %" PRId64 " bytes",
          path.c_str(), fragments.size(), region_size);
    done = true;
  } while (false);

  close(fd);
  return done;
}

}  // namespace zm_mp4