Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/mp4/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 48 kB image not shown  

Quelle  MoofParser.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "MoofParser.h"

#include <limits>

#include "Box.h"
#include "MP4Interval.h"
#include "MediaDataDemuxer.h"
#include "SinfParser.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/HelperMacros.h"
#include "mozilla/Logging.h"
#include "mozilla/Try.h"

#define LOG_ERROR(name, arg, ...)                                             \
  MOZ_LOG_FMT(gMediaDemuxerLog, mozilla::LogLevel::Error,                     \
              MOZ_STRINGIFY(name) "({})::{}: " arg, fmt::ptr(this), __func__, \
              ##__VA_ARGS__)
#define LOG_WARN(name, arg, ...)                                              \
  MOZ_LOG_FMT(gMediaDemuxerLog, mozilla::LogLevel::Warning,                   \
              MOZ_STRINGIFY(name) "({})::{}: " arg, fmt::ptr(this), __func__, \
              ##__VA_ARGS__)
#define LOG_DEBUG(name, arg, ...)                                             \
  MOZ_LOG_FMT(gMediaDemuxerLog, mozilla::LogLevel::Debug,                     \
              MOZ_STRINGIFY(name) "({})::{}: " arg, fmt::ptr(this), __func__, \
              ##__VA_ARGS__)

namespace mozilla {

using TimeUnit = media::TimeUnit;

const uint32_t kKeyIdSize = 16;

namespace {

Result<MediaByteRange, nsresult> MakeMediaByteRangeChecked(
    const CheckedUint64& aStart, uint64_t aLength) {
  CheckedUint64 end = aStart + aLength;
  if (!aStart.isValid() || !end.isValid() ||
      aStart.value() > uint64_t(INT64_MAX) ||
      end.value() > uint64_t(INT64_MAX)) {
    return Err(NS_ERROR_FAILURE);
  }
  return MediaByteRange(static_cast<int64_t>(aStart.value()),
                        static_cast<int64_t>(end.value()));
}

}  // namespace

bool MoofParser::RebuildFragmentedIndex(const MediaByteRangeSet& aByteRanges) {
  BoxContext context(mSource, aByteRanges);
  return RebuildFragmentedIndex(context);
}

bool MoofParser::RebuildFragmentedIndex(const MediaByteRangeSet& aByteRanges,
                                        bool* aCanEvict) {
  MOZ_ASSERT(aCanEvict);
  if (*aCanEvict && mMoofs.Length() > 1) {
    MOZ_ASSERT(mMoofs.Length() == mMediaRanges.Length());
    mMoofs.RemoveElementsAt(0, mMoofs.Length() - 1);
    mMediaRanges.RemoveElementsAt(0, mMediaRanges.Length() - 1);
    *aCanEvict = true;
  } else {
    *aCanEvict = false;
  }
  return RebuildFragmentedIndex(aByteRanges);
}

bool MoofParser::RebuildFragmentedIndex(BoxContext& aContext) {
  LOG_DEBUG(
      Moof, "Starting, mTrackParseMode={}, track#={} (ignore if multitrack).",
      mTrackParseMode.is<ParseAllTracks>() ? "multitrack" : "single track",
      mTrackParseMode.is<ParseAllTracks>() ? 0
                                           : mTrackParseMode.as<uint32_t>());
  bool foundValidMoof = false;

  for (Box box(&aContext, mOffset); box.IsAvailable();
       mOffset = box.NextOffset(), box = box.Next()) {
    if (box.IsType("moov") && mInitRange.IsEmpty()) {
      mInitRange = MediaByteRange(0, box.Range().mEnd);
      ParseMoov(box);
    } else if (box.IsType("moof")) {
      Moof moof(box, mTrackParseMode, mTrex, mMvhd, mMdhd, mEdts, mSinf,
                mIsAudio, &mLastDecodeTime, mTracksEndCts);

      if (!moof.IsValid()) {
        continue;  // Skip to next box.
      }

      if (!mMoofs.IsEmpty()) {
        // Stitch time ranges together in the case of a (hopefully small) time
        // range gap between moofs.
        mMoofs.LastElement().FixRounding(moof);
      }

      mMediaRanges.AppendElement(moof.mRange);
      mMoofs.AppendElement(std::move(moof));
      foundValidMoof = true;
    } else if (box.IsType("mdat") && !Moofs().IsEmpty()) {
      // Check if we have all our data from last moof.
      Moof& moof = Moofs().LastElement();
      media::Interval<int64_t> datarange(moof.mMdatRange.mStart,
                                         moof.mMdatRange.mEnd, 0);
      media::Interval<int64_t> mdat(box.Range().mStart, box.Range().mEnd, 0);
      if (datarange.Intersects(mdat)) {
        mMediaRanges.LastElement() =
            mMediaRanges.LastElement().Span(box.Range());
      }
    }
  }
  MOZ_ASSERT(mTrackParseMode.is<ParseAllTracks>() ||
                 mTrex.mTrackId == mTrackParseMode.as<uint32_t>(),
             "If not parsing all tracks, mTrex should have the same track id "
             "as the track being parsed.");
  LOG_DEBUG(Moof, "Done, foundValidMoof={}.",
            foundValidMoof ? "true" : "false");
  return foundValidMoof;
}

MediaByteRange MoofParser::FirstCompleteMediaHeader() {
  if (Moofs().IsEmpty()) {
    return MediaByteRange();
  }
  return Moofs()[0].mRange;
}

MediaByteRange MoofParser::FirstCompleteMediaSegment() {
  for (uint32_t i = 0; i < mMediaRanges.Length(); i++) {
    if (mMediaRanges[i].Contains(Moofs()[i].mMdatRange)) {
      return mMediaRanges[i];
    }
  }
  return MediaByteRange();
}

const CencSampleEncryptionInfoEntry* MoofParser::GetSampleEncryptionEntry(
    size_t aMoof, size_t aSample) const {
  if (aMoof >= mMoofs.Length()) {
    return nullptr;
  }
  return mMoofs[aMoof].GetSampleEncryptionEntry(
      aSample, &mTrackSampleToGroupEntries, &mTrackSampleEncryptionInfoEntries);
}

DDLoggedTypeDeclNameAndBase(BlockingStream, ByteStream);

class BlockingStream : public ByteStream,
                       public DecoderDoctorLifeLogger<BlockingStream> {
 public:
  explicit BlockingStream(ByteStream* aStream) : mStream(aStream) {
    DDLINKCHILD("stream", aStream);
  }

  nsresult ReadAt(int64_t offset, void* data, size_t size,
                  size_t* bytes_read) override {
    return mStream->ReadAt(offset, data, size, bytes_read);
  }

  nsresult CachedReadAt(int64_t offset, void* data, size_t size,
                        size_t* bytes_read) override {
    return mStream->ReadAt(offset, data, size, bytes_read);
  }

  virtual bool Length(int64_t* size) override { return mStream->Length(size); }

 private:
  RefPtr<ByteStream> mStream;
};

nsresult MoofParser::BlockingReadNextMoof() {
  LOG_DEBUG(Moof, "Starting.");
  int64_t length = std::numeric_limits<int64_t>::max();
  mSource->Length(&length);
  RefPtr<BlockingStream> stream = new BlockingStream(mSource);
  MediaByteRangeSet byteRanges(MediaByteRange(0, length));

  BoxContext context(stream, byteRanges);
  Box box(&context, mOffset);
  for (; box.IsAvailable(); box = box.Next()) {
    if (box.IsType("moof")) {
      MediaByteRangeSet parseByteRanges(
          MediaByteRange(mOffset, box.Range().mEnd));
      BoxContext parseContext(stream, parseByteRanges);
      if (RebuildFragmentedIndex(parseContext)) {
        LOG_DEBUG(Moof, "Succeeded on RebuildFragmentedIndex, returning NS_OK");
        return NS_OK;
      }
    }
  }
  nsresult rv = box.Offset() == length ? NS_ERROR_DOM_MEDIA_END_OF_STREAM
                                       : box.InitStatus();
  LOG_DEBUG(Moof, "Couldn't read next moof, returning {}",
            GetStaticErrorName(rv));
  return rv;
}

void MoofParser::ScanForMetadata(mozilla::MediaByteRange& aMoov) {
  LOG_DEBUG(Moof, "Starting.");
  int64_t length = std::numeric_limits<int64_t>::max();
  mSource->Length(&length);
  MediaByteRangeSet byteRanges;
  byteRanges += MediaByteRange(0, length);
  RefPtr<BlockingStream> stream = new BlockingStream(mSource);

  BoxContext context(stream, byteRanges);
  for (Box box(&context, mOffset); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("moov")) {
      aMoov = box.Range();
      break;
    }
  }
  mInitRange = aMoov;
  LOG_DEBUG(Moof, "Done, mInitRange.mStart={}, mInitRange.mEnd={}",
            mInitRange.mStart, mInitRange.mEnd);
}

already_AddRefed<mozilla::MediaByteBuffer> MoofParser::Metadata() {
  LOG_DEBUG(Moof, "Starting.");
  MediaByteRange moov;
  ScanForMetadata(moov);
  CheckedInt<MediaByteBuffer::size_type> moovLength = moov.Length();
  if (!moovLength.isValid() || !moovLength.value()) {
    // No moov, or cannot be used as array size.
    LOG_WARN(Moof,
             "Did not get usable moov length while trying to parse Metadata.");
    return nullptr;
  }

  RefPtr<MediaByteBuffer> metadata = new MediaByteBuffer();
  if (!metadata->SetLength(moovLength.value(), fallible)) {
    LOG_ERROR(Moof, "OOM");
    return nullptr;
  }

  RefPtr<BlockingStream> stream = new BlockingStream(mSource);
  size_t read;
  nsresult rv = stream->ReadAt(moov.mStart, metadata->Elements(),
                               moovLength.value(), &read);
  if (NS_FAILED(rv) || read != moovLength.value()) {
    LOG_WARN(Moof, "Failed to read moov while trying to parse Metadata.");
    return nullptr;
  }
  LOG_DEBUG(Moof, "Done, found metadata.");
  return metadata.forget();
}

MP4Interval<TimeUnit> MoofParser::GetCompositionRange(
    const MediaByteRangeSet& aByteRanges) {
  LOG_DEBUG(Moof, "Starting.");
  MP4Interval<TimeUnit> compositionRange;
  BoxContext context(mSource, aByteRanges);
  for (size_t i = 0; i < mMoofs.Length(); i++) {
    Moof& moof = mMoofs[i];
    Box box(&context, moof.mRange.mStart);
    if (box.IsAvailable()) {
      compositionRange = compositionRange.Extents(moof.mTimeRange);
    }
  }
  LOG_DEBUG(Moof, "Done, compositionRange.start={}, compositionRange.end={}.",
            compositionRange.start.ToMicroseconds(),
            compositionRange.end.ToMicroseconds());
  return compositionRange;
}

bool MoofParser::ReachedEnd() {
  int64_t length;
  return mSource->Length(&length) && mOffset == length;
}

void MoofParser::ParseMoov(const Box& aBox) {
  LOG_DEBUG(Moof, "Starting.");
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("mvhd")) {
      mMvhd = Mvhd(box);
    } else if (box.IsType("trak")) {
      ParseTrak(box);
    } else if (box.IsType("mvex")) {
      ParseMvex(box);
    }
  }
  LOG_DEBUG(Moof, "Done.");
}

void MoofParser::ParseTrak(const Box& aBox) {
  LOG_DEBUG(Trak, "Starting.");
  Tkhd tkhd;
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("tkhd")) {
      tkhd = Tkhd(box);
    } else if (box.IsType("mdia")) {
      if (mTrackParseMode.is<ParseAllTracks>() ||
          tkhd.mTrackId == mTrackParseMode.as<uint32_t>()) {
        ParseMdia(box);
      }
    } else if (box.IsType("edts") &&
               (mTrackParseMode.is<ParseAllTracks>() ||
                tkhd.mTrackId == mTrackParseMode.as<uint32_t>())) {
      mEdts = Edts(box);
    }
  }
  LOG_DEBUG(Trak, "Done.");
}

void MoofParser::ParseMdia(const Box& aBox) {
  LOG_DEBUG(Mdia, "Starting.");
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("mdhd")) {
      mMdhd = Mdhd(box);
    } else if (box.IsType("minf")) {
      ParseMinf(box);
    }
  }
  LOG_DEBUG(Mdia, "Done.");
}

void MoofParser::ParseMvex(const Box& aBox) {
  LOG_DEBUG(Mvex, "Starting.");
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("trex")) {
      Trex trex = Trex(box);
      if (mTrackParseMode.is<ParseAllTracks>() ||
          trex.mTrackId == mTrackParseMode.as<uint32_t>()) {
        mTrex = trex;
      }
    }
  }
  LOG_DEBUG(Mvex, "Done.");
}

void MoofParser::ParseMinf(const Box& aBox) {
  LOG_DEBUG(Minf, "Starting.");
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("stbl")) {
      ParseStbl(box);
    }
  }
  LOG_DEBUG(Minf, "Done.");
}

void MoofParser::ParseStbl(const Box& aBox) {
  LOG_DEBUG(Stbl, "Starting.");
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("stsd")) {
      ParseStsd(box);
    } else if (box.IsType("sgpd")) {
      Sgpd sgpd(box);
      if (sgpd.IsValid() && sgpd.mGroupingType == "seig") {
        mTrackSampleEncryptionInfoEntries.Clear();
        if (!mTrackSampleEncryptionInfoEntries.AppendElements(
                sgpd.mEntries, mozilla::fallible)) {
          LOG_ERROR(Stbl, "OOM");
          return;
        }
      }
    } else if (box.IsType("sbgp")) {
      Sbgp sbgp(box);
      if (sbgp.IsValid() && sbgp.mGroupingType == "seig") {
        mTrackSampleToGroupEntries.Clear();
        if (!mTrackSampleToGroupEntries.AppendElements(sbgp.mEntries,
                                                       mozilla::fallible)) {
          LOG_ERROR(Stbl, "OOM");
          return;
        }
      }
    }
  }
  LOG_DEBUG(Stbl, "Done.");
}

void MoofParser::ParseStsd(const Box& aBox) {
  LOG_DEBUG(Stsd, "Starting.");
  if (mTrackParseMode.is<ParseAllTracks>()) {
    // It is not a sane operation to try and map sample description boxes from
    // multiple tracks onto the parser, which is modeled around storing metadata
    // for a single track.
    LOG_DEBUG(Stsd, "Early return due to multitrack parser.");
    return;
  }
  MOZ_ASSERT(
      mSampleDescriptions.IsEmpty(),
      "Shouldn't have any sample descriptions yet when starting to parse stsd");
  uint32_t numberEncryptedEntries = 0;
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    SampleDescriptionEntry sampleDescriptionEntry{false};
    if (box.IsType("encv") || box.IsType("enca")) {
      ParseEncrypted(box);
      sampleDescriptionEntry.mIsEncryptedEntry = true;
      numberEncryptedEntries++;
    }
    if (!mSampleDescriptions.AppendElement(sampleDescriptionEntry,
                                           mozilla::fallible)) {
      LOG_ERROR(Stsd, "OOM");
      return;
    }
  }
  if (mSampleDescriptions.IsEmpty()) {
    LOG_WARN(Stsd,
             "No sample description entries found while parsing Stsd! This "
             "shouldn't happen, as the spec requires one for each track!");
  }
  if (numberEncryptedEntries > 1) {
    LOG_WARN(Stsd,
             "More than one encrypted sample description entry found while "
             "parsing track! We don't expect this, and it will likely break "
             "during fragment look up!");
  }
  LOG_DEBUG(Stsd,
            "Done, numberEncryptedEntries={}, mSampleDescriptions.Length={}",
            numberEncryptedEntries, mSampleDescriptions.Length());
}

void MoofParser::ParseEncrypted(const Box& aBox) {
  LOG_DEBUG(Moof, "Starting.");
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    // Some MP4 files have been found to have multiple sinf boxes in the same
    // enc* box. This does not match spec anyway, so just choose the first
    // one that parses properly.
    if (box.IsType("sinf")) {
      mSinf = Sinf(box);

      if (mSinf.IsValid()) {
        break;
      }
    }
  }
  LOG_DEBUG(Moof, "Done.");
}

class CtsComparator {
 public:
  bool Equals(Sample* const aA, Sample* const aB) const {
    return aA->mCompositionRange.start == aB->mCompositionRange.start;
  }
  bool LessThan(Sample* const aA, Sample* const aB) const {
    return aA->mCompositionRange.start < aB->mCompositionRange.start;
  }
};

Moof::Moof(const Box& aBox, const TrackParseMode& aTrackParseMode,
           const Trex& aTrex, const Mvhd& aMvhd, const Mdhd& aMdhd,
           const Edts& aEdts, const Sinf& aSinf, const bool aIsAudio,
           uint64_t* aDecodeTime, nsTArray<TrackEndCts>& aTracksEndCts)
    : mRange(aBox.Range()),
      mTfhd(aTrex),
      // Do not reporting discontuities less than 35ms
      mMaxRoundingError(TimeUnit::FromSeconds(0.035)) {
  LOG_DEBUG(
      Moof, "Starting, aTrackParseMode={}, track#={} (ignore if multitrack).",
      aTrackParseMode.is<ParseAllTracks>() ? "multitrack" : "single track",
      aTrackParseMode.is<ParseAllTracks>() ? 0
                                           : aTrackParseMode.as<uint32_t>());
  MOZ_ASSERT(aTrackParseMode.is<ParseAllTracks>() ||
                 aTrex.mTrackId == aTrackParseMode.as<uint32_t>(),
             "If not parsing all tracks, aTrex should have the same track id "
             "as the track being parsed.");
  nsTArray<Box> psshBoxes;
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("traf")) {
      ParseTraf(box, aTrackParseMode, aTrex, aMvhd, aMdhd, aEdts, aSinf,
                aIsAudio, aDecodeTime);
    }
    if (box.IsType("pssh")) {
      psshBoxes.AppendElement(box);
    }
  }

  // The EME spec requires that PSSH boxes which are contiguous in the
  // file are dispatched to the media element in a single "encrypted" event.
  // So append contiguous boxes here.
  for (size_t i = 0; i < psshBoxes.Length(); ++i) {
    const Box& box = psshBoxes[i];
    if (i == 0 || box.Offset() != psshBoxes[i - 1].NextOffset()) {
      mPsshes.AppendElement();
    }
    nsTArray<uint8_t>& pssh = mPsshes.LastElement();
    pssh.AppendElements(std::move(box.ReadCompleteBox()));
  }

  if (IsValid()) {
    if (mIndex.Length()) {
      // Ensure the samples are contiguous with no gaps.
      nsTArray<Sample*> ctsOrder;
      for (auto& sample : mIndex) {
        ctsOrder.AppendElement(&sample);
      }
      ctsOrder.Sort(CtsComparator());

      for (size_t i = 1; i < ctsOrder.Length(); i++) {
        ctsOrder[i - 1]->mCompositionRange.end =
            ctsOrder[i]->mCompositionRange.start;
      }

      // Ensure that there are no gaps between the first sample in this
      // Moof and the preceeding Moof.
      if (!ctsOrder.IsEmpty()) {
        bool found = false;
        // Track ID of the track we're parsing.
        const uint32_t trackId = aTrex.mTrackId;
        // Find the previous CTS end time of Moof preceeding the Moofs we just
        // parsed, for the track we're parsing.
        for (auto& prevCts : aTracksEndCts) {
          if (prevCts.mTrackId == trackId) {
            // We ensure there are no gaps in samples' CTS between the last
            // sample in a Moof, and the first sample in the next Moof, if
            // they're within these many Microseconds of each other.
            const TimeUnit CROSS_MOOF_CTS_MERGE_THRESHOLD =
                TimeUnit::FromMicroseconds(1);
            // We have previously parsed a Moof for this track. Smooth the gap
            // between samples for this track across the Moof bounary.
            if (ctsOrder[0]->mCompositionRange.start > prevCts.mCtsEndTime &&
                ctsOrder[0]->mCompositionRange.start - prevCts.mCtsEndTime <=
                    CROSS_MOOF_CTS_MERGE_THRESHOLD) {
              ctsOrder[0]->mCompositionRange.start = prevCts.mCtsEndTime;
            }
            prevCts.mCtsEndTime = ctsOrder.LastElement()->mCompositionRange.end;
            found = true;
            break;
          }
        }
        if (!found) {
          // We've not parsed a Moof for this track yet. Save its CTS end
          // time for the next Moof we parse.
          aTracksEndCts.AppendElement(TrackEndCts(
              trackId, ctsOrder.LastElement()->mCompositionRange.end));
        }
      }

      // In MP4, the duration of a sample is defined as the delta between two
      // decode timestamps. The operation above has updated the duration of each
      // sample as a Sample's duration is mCompositionRange.end -
      // mCompositionRange.start MSE's TrackBuffersManager expects dts that
      // increased by the sample's duration, so we rewrite the dts accordingly.
      TimeUnit presentationDuration =
          ctsOrder.LastElement()->mCompositionRange.end -
          ctsOrder[0]->mCompositionRange.start;
      auto decodeOffset =
          aMdhd.ToTimeUnit(CheckedInt64(*aDecodeTime) - aEdts.mMediaStart);
      auto offsetOffset = aMvhd.ToTimeUnit(aEdts.mEmptyOffset);
      TimeUnit endDecodeTime =
          (decodeOffset.isOk() && offsetOffset.isOk())
              ? decodeOffset.unwrap() + offsetOffset.unwrap()
              : TimeUnit::Zero(aMvhd.mTimescale);
      TimeUnit decodeDuration = endDecodeTime - mIndex[0].mDecodeTime;
      double adjust = 0.;
      if (!presentationDuration.IsZero()) {
        double num = decodeDuration.ToSeconds();
        double denom = presentationDuration.ToSeconds();
        if (denom != 0.) {
          adjust = num / denom;
        }
      }

      TimeUnit dtsOffset = mIndex[0].mDecodeTime;
      TimeUnit compositionDuration(0, aMvhd.mTimescale);
      // Adjust the dts, ensuring that the new adjusted dts will never be
      // greater than decodeTime (the next moof's decode start time).
      for (auto& sample : mIndex) {
        sample.mDecodeTime = dtsOffset + compositionDuration.MultDouble(adjust);
        compositionDuration += sample.mCompositionRange.Length();
      }
      mTimeRange =
          MP4Interval<TimeUnit>(ctsOrder[0]->mCompositionRange.start,
                                ctsOrder.LastElement()->mCompositionRange.end);
    }
    // No need to retrieve auxiliary encryption data if we have a senc box: we
    // won't use it in SampleIterator::GetNext()
    if (!mSencValid) {
      ProcessCencAuxInfo(aSinf.mDefaultEncryptionType);
    }
  }
  LOG_DEBUG(Moof, "Done.");
}

bool Moof::GetAuxInfo(AtomType aType,
                      FallibleTArray<MediaByteRange>* aByteRanges) {
  LOG_DEBUG(Moof, "Starting.");
  aByteRanges->Clear();

  Saiz* saiz = nullptr;
  for (int i = 0;; i++) {
    if (i == mSaizs.Length()) {
      LOG_DEBUG(Moof, "Could not find saiz matching aType. Returning false.");
      return false;
    }
    if (mSaizs[i].mAuxInfoType == aType) {
      saiz = &mSaizs[i];
      break;
    }
  }
  Saio* saio = nullptr;
  for (int i = 0;; i++) {
    if (i == mSaios.Length()) {
      LOG_DEBUG(Moof, "Could not find saio matching aType. Returning false.");
      return false;
    }
    if (mSaios[i].mAuxInfoType == aType) {
      saio = &mSaios[i];
      break;
    }
  }

  if (saio->mOffsets.Length() == 1) {
    if (!aByteRanges->SetCapacity(saiz->mSampleInfoSize.Length(),
                                  mozilla::fallible)) {
      LOG_ERROR(Moof, "OOM");
      return false;
    }
    CheckedUint64 offset =
        CheckedUint64(mTfhd.mBaseDataOffset) + CheckedUint64(saio->mOffsets[0]);
    for (size_t i = 0; i < saiz->mSampleInfoSize.Length(); i++) {
      auto range = MakeMediaByteRangeChecked(offset, saiz->mSampleInfoSize[i]);
      if (range.isErr()) {
        LOG_WARN(Moof, "Invalid aux info offset");
        return false;
      }
      if (!aByteRanges->AppendElement(range.unwrap(), mozilla::fallible)) {
        LOG_ERROR(Moof, "OOM");
        return false;
      }
      offset += saiz->mSampleInfoSize[i];
    }
    LOG_DEBUG(
        Moof,
        "Saio has 1 entry. aByteRanges populated accordingly. Returning true.");
    return true;
  }

  if (saio->mOffsets.Length() == saiz->mSampleInfoSize.Length()) {
    if (!aByteRanges->SetCapacity(saiz->mSampleInfoSize.Length(),
                                  mozilla::fallible)) {
      LOG_ERROR(Moof, "OOM");
      return false;
    }
    for (size_t i = 0; i < saio->mOffsets.Length(); i++) {
      CheckedUint64 offset =
          CheckedUint64(mRange.mStart) + CheckedUint64(saio->mOffsets[i]);
      auto range = MakeMediaByteRangeChecked(offset, saiz->mSampleInfoSize[i]);
      if (range.isErr()) {
        LOG_WARN(Moof, "Invalid aux info offset");
        return false;
      }
      if (!aByteRanges->AppendElement(range.unwrap(), mozilla::fallible)) {
        LOG_ERROR(Moof, "OOM");
        return false;
      }
    }
    LOG_DEBUG(
        Moof,
        "Saio and saiz have same number of entries. aByteRanges populated "
        "accordingly. Returning true.");
    return true;
  }

  LOG_DEBUG(Moof,
            "Moof::GetAuxInfo could not find any Aux info, returning false.");
  return false;
}

bool Moof::ProcessCencAuxInfo(AtomType aScheme) {
  LOG_DEBUG(Moof, "Starting.");
  FallibleTArray<MediaByteRange> cencRanges;
  if (!GetAuxInfo(aScheme, &cencRanges) ||
      cencRanges.Length() != mIndex.Length()) {
    LOG_DEBUG(Moof, "Couldn't find cenc aux info.");
    return false;
  }
  for (int i = 0; i < cencRanges.Length(); i++) {
    mIndex[i].mCencRange = cencRanges[i];
  }
  LOG_DEBUG(Moof, "Found cenc aux info and stored on index.");
  return true;
}

const CencSampleEncryptionInfoEntry* Moof::GetSampleEncryptionEntry(
    size_t aSample,
    const FallibleTArray<SampleToGroupEntry>* aTrackSampleToGroupEntries,
    const FallibleTArray<CencSampleEncryptionInfoEntry>*
        aTrackSampleEncryptionInfoEntries) const {
  const SampleToGroupEntry* sampleToGroupEntry = nullptr;

  // Default to using the sample to group entries for the fragment, otherwise
  // fall back to the sample to group entries for the track.
  const FallibleTArray<SampleToGroupEntry>* sampleToGroupEntries =
      mFragmentSampleToGroupEntries.Length() != 0
          ? &mFragmentSampleToGroupEntries
          : aTrackSampleToGroupEntries;

  if (!sampleToGroupEntries) {
    return nullptr;
  }

  uint32_t seen = 0;

  for (const SampleToGroupEntry& entry : *sampleToGroupEntries) {
    if (seen + entry.mSampleCount > aSample) {
      sampleToGroupEntry = &entry;
      break;
    }
    seen += entry.mSampleCount;
  }

  // ISO-14496-12 Section 8.9.2.3 and 8.9.4 : group description index
  // (1) ranges from 1 to the number of sample group entries in the track
  // level SampleGroupDescription Box, or (2) takes the value 0 to
  // indicate that this sample is a member of no group, in this case, the
  // sample is associated with the default values specified in
  // TrackEncryption Box, or (3) starts at 0x10001, i.e. the index value
  // 1, with the value 1 in the top 16 bits, to reference fragment-local
  // SampleGroupDescription Box.

  // According to the spec, ISO-14496-12, the sum of the sample counts in this
  // box should be equal to the total number of samples, and, if less, the
  // reader should behave as if an extra SampleToGroupEntry existed, with
  // groupDescriptionIndex 0.

  if (!sampleToGroupEntry || sampleToGroupEntry->mGroupDescriptionIndex == 0) {
    return nullptr;
  }

  const FallibleTArray<CencSampleEncryptionInfoEntry>* entries =
      aTrackSampleEncryptionInfoEntries;

  uint32_t groupIndex = sampleToGroupEntry->mGroupDescriptionIndex;

  // If the first bit is set to a one, then we should use the sample group
  // descriptions from the fragment.
  if (groupIndex > SampleToGroupEntry::kFragmentGroupDescriptionIndexBase) {
    groupIndex -= SampleToGroupEntry::kFragmentGroupDescriptionIndexBase;
    entries = &mFragmentSampleEncryptionInfoEntries;
  }

  if (!entries) {
    return nullptr;
  }

  // The group_index is one based.
  return groupIndex > entries->Length() ? nullptr
                                        : &entries->ElementAt(groupIndex - 1);
}

void Moof::ParseTraf(const Box& aBox, const TrackParseMode& aTrackParseMode,
                     const Trex& aTrex, const Mvhd& aMvhd, const Mdhd& aMdhd,
                     const Edts& aEdts, const Sinf& aSinf, const bool aIsAudio,
                     uint64_t* aDecodeTime) {
  LOG_DEBUG(
      Traf, "Starting, aTrackParseMode={}, track#={} (ignore if multitrack).",
      aTrackParseMode.is<ParseAllTracks>() ? "multitrack" : "single track",
      aTrackParseMode.is<ParseAllTracks>() ? 0
                                           : aTrackParseMode.as<uint32_t>());
  MOZ_ASSERT(aDecodeTime);
  MOZ_ASSERT(aTrackParseMode.is<ParseAllTracks>() ||
                 aTrex.mTrackId == aTrackParseMode.as<uint32_t>(),
             "If not parsing all tracks, aTrex should have the same track id "
             "as the track being parsed.");
  Tfdt tfdt;

  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("tfhd")) {
      mTfhd = Tfhd(box, aTrex);
    } else if (aTrackParseMode.is<ParseAllTracks>() ||
               mTfhd.mTrackId == aTrackParseMode.as<uint32_t>()) {
      if (box.IsType("tfdt")) {
        tfdt = Tfdt(box);
      } else if (box.IsType("sgpd")) {
        Sgpd sgpd(box);
        if (sgpd.IsValid() && sgpd.mGroupingType == "seig") {
          mFragmentSampleEncryptionInfoEntries.Clear();
          if (!mFragmentSampleEncryptionInfoEntries.AppendElements(
                  sgpd.mEntries, mozilla::fallible)) {
            LOG_ERROR(Moof, "OOM");
            return;
          }
        }
      } else if (box.IsType("sbgp")) {
        Sbgp sbgp(box);
        if (sbgp.IsValid() && sbgp.mGroupingType == "seig") {
          mFragmentSampleToGroupEntries.Clear();
          if (!mFragmentSampleToGroupEntries.AppendElements(
                  sbgp.mEntries, mozilla::fallible)) {
            LOG_ERROR(Moof, "OOM");
            return;
          }
        }
      } else if (box.IsType("saiz")) {
        if (!mSaizs.AppendElement(Saiz(box, aSinf.mDefaultEncryptionType),
                                  mozilla::fallible)) {
          LOG_ERROR(Moof, "OOM");
          return;
        }
      } else if (box.IsType("saio")) {
        if (!mSaios.AppendElement(Saio(box, aSinf.mDefaultEncryptionType),
                                  mozilla::fallible)) {
          LOG_ERROR(Moof, "OOM");
          return;
        }
      }
    }
  }
  if (aTrackParseMode.is<uint32_t>() &&
      mTfhd.mTrackId != aTrackParseMode.as<uint32_t>()) {
    LOG_DEBUG(Traf,
              "Early return as not multitrack parser and track id didn't match "
              "mTfhd.mTrackId={}",
              mTfhd.mTrackId);
    return;
  }
  // Second pass: search for trun boxes and senc boxes.
  uint64_t decodeTime =
      tfdt.IsValid() ? tfdt.mBaseMediaDecodeTime : *aDecodeTime;
  Box sencBox;
  for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
    if (box.IsType("trun")) {
      if (ParseTrun(box, aMvhd, aMdhd, aEdts, aIsAudio, &decodeTime).isOk()) {
        mValid = true;
      } else {
        LOG_WARN(Moof, "ParseTrun failed");
        mValid = false;
        return;
      }
    } else if (box.IsType("senc")) {
      LOG_DEBUG(Moof, "Found senc box");
      sencBox = box;
    }
  }

  // senc box found: parse it.
  // We need to parse senc boxes in another pass because we need potential sgpd
  // and sbgp boxes to have been parsed, as they might override the IV size and
  // as such the size of senc entries.
  // trun box shall have been parsed as well, so mIndex has been filled.
  if (sencBox.IsAvailable()) {
    if (ParseSenc(sencBox, aSinf).isErr()) [[unlikely]] {
      LOG_WARN(Moof, "ParseSenc failed");
    }
  }

  *aDecodeTime = decodeTime;
  LOG_DEBUG(Traf, "Done, setting aDecodeTime={}.", decodeTime);
}

void Moof::FixRounding(const Moof& aMoof) {
  TimeUnit gap = aMoof.mTimeRange.start - mTimeRange.end;
  if (gap.IsPositive() && gap <= mMaxRoundingError) {
    mTimeRange.end = aMoof.mTimeRange.start;
  }
}

Result<Ok, nsresult> Moof::ParseSenc(const Box& aBox, const Sinf& aSinf) {
  // If we already had a senc box, ignore following ones
  // Not sure how likely this could be in real life
  if (mSencValid) [[unlikely]] {
    LOG_WARN(Moof, "Already found a valid senc box, ignoring new one");
    return Ok();
  }

  BoxReader reader(aBox);
  const uint8_t version = MOZ_TRY(reader->ReadU8());
  const uint32_t flags = MOZ_TRY(reader->ReadU24());
  const uint32_t sampleCount = MOZ_TRY(reader->ReadU32());
  // ISO/IEC 23001-7 §7.2:
  // "sample_count is the number of protected samples in the containing track or
  // track fragment. This value SHALL be either zero (0) or the total number of
  // samples in the track or track fragment."
  if (sampleCount == 0) {
    LOG_DEBUG(Moof, "senc box has 0 sample_count");
    // Though having sample_count = 0 seems to be compliant, return without
    // error but don't set mSencValid to true in case there is another senc box
    // or saio/saiz auxiliary data
    return Ok();
  }
  if (sampleCount != mIndex.Length()) {
    LOG_ERROR(Moof, "Invalid sample count in senc box: expecting {}, got {}\n",
              mIndex.Length(), sampleCount);
    return Err(NS_ERROR_FAILURE);
  }

  if (version == 0) {
    for (size_t i = 0; i < sampleCount; ++i) {
      Sample& sample = mIndex[i];
      const CencSampleEncryptionInfoEntry* sampleInfo =
          GetSampleEncryptionEntry(i);
      uint8_t ivSize = sampleInfo ? sampleInfo->mIVSize : aSinf.mDefaultIVSize;
      if (!reader->ReadArray(sample.mIV, ivSize)) {
        return Err(MediaResult::Logged(
            NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
            RESULT_DETAIL("sample InitializationVector error"),
            gMediaDemuxerLog));
      }
      // Clear arrays, to be safe, in the (unlikely and invalid) case we started
      // to parse a previous senc box but it failed halfway.
      sample.mPlainSizes.Clear();
      sample.mEncryptedSizes.Clear();
      const bool useSubSampleEncryption = flags & 0x02;
      if (useSubSampleEncryption) {
        uint16_t subsampleCount = MOZ_TRY(reader->ReadU16());
        for (uint16_t i = 0; i < subsampleCount; ++i) {
          uint16_t bytesOfClearData = MOZ_TRY(reader->ReadU16());
          uint32_t bytesOfProtectedData = MOZ_TRY(reader->ReadU32());
          sample.mPlainSizes.AppendElement(bytesOfClearData);
          sample.mEncryptedSizes.AppendElement(bytesOfProtectedData);
        }
      } else {
        // No UseSubSampleEncryption flag means the entire sample is encrypted.
        sample.mPlainSizes.AppendElement(0);
        sample.mEncryptedSizes.AppendElement(sample.mByteRange.Length());
      }
    }
  } else if (version == 1) {
    // TODO
    LOG_ERROR(Senc, "version {} not supported yet", version);
    return Err(NS_ERROR_FAILURE);
  } else if (version == 2) {
    // TODO
    LOG_ERROR(Senc, "version {} not supported yet", version);
    return Err(NS_ERROR_FAILURE);
  } else {
    LOG_ERROR(Senc, "Unknown version {}", version);
    return Err(NS_ERROR_FAILURE);
  }
  mSencValid = true;
  return Ok();
}

Result<Ok, nsresult> Moof::ParseTrun(const Box& aBox, const Mvhd& aMvhd,
                                     const Mdhd& aMdhd, const Edts& aEdts,
                                     const bool aIsAudio,
                                     uint64_t* aDecodeTime) {
  LOG_DEBUG(Trun, "Starting.");
  if (!mTfhd.IsValid() || !aMvhd.IsValid() || !aMdhd.IsValid() ||
      !aEdts.IsValid()) {
    LOG_WARN(
        Moof, "Invalid dependencies: mTfhd({}) aMvhd({}) aMdhd({}) aEdts({})",
        mTfhd.IsValid(), aMvhd.IsValid(), aMdhd.IsValid(), !aEdts.IsValid());
    return Err(NS_ERROR_FAILURE);
  }

  BoxReader reader(aBox);
  if (!reader->CanReadType<uint32_t>()) {
    LOG_WARN(Moof, "Incomplete Box (missing flags)");
    return Err(NS_ERROR_FAILURE);
  }
  uint32_t flags = MOZ_TRY(reader->ReadU32());

  if (!reader->CanReadType<uint32_t>()) {
    LOG_WARN(Moof, "Incomplete Box (missing sampleCount)");
    return Err(NS_ERROR_FAILURE);
  }
  uint32_t sampleCount = MOZ_TRY(reader->ReadU32());
  if (sampleCount == 0) {
    LOG_DEBUG(Trun, "Trun with no samples, returning.");
    return Ok();
  }

  CheckedUint64 offset = mTfhd.mBaseDataOffset;
  if (flags & 0x01) {
    offset += MOZ_TRY(reader->ReadU32());
  }
  uint32_t firstSampleFlags = mTfhd.mDefaultSampleFlags;
  if (flags & 0x04) {
    firstSampleFlags = MOZ_TRY(reader->ReadU32());
  }

  // Bug 2004835: use CheckedInt to make sure we don't overflow.
  // Reinterpret the tfdt as signed before assigning to CheckedInt.
  // Some encoders store AAC pre-roll delay as 2^64-N (e.g. 2^64-2048), which
  // is valid per ISO 14496-12 but exceeds INT64_MAX. The static_cast gives a
  // small negative value (-N) that the existing edit-list mechanism treats as
  // pre-roll, keeping the presentation timestamps correct. Accumulation
  // overflow is still caught by the isValid() check below.
  CheckedInt64 decodeTime = static_cast<int64_t>(*aDecodeTime);

  if (!mIndex.SetCapacity(mIndex.Length() + sampleCount, fallible)) {
    LOG_ERROR(Moof, "Out of Memory");
    return Err(NS_ERROR_FAILURE);
  }

  for (size_t i = 0; i < sampleCount; i++) {
    uint32_t sampleDuration = mTfhd.mDefaultSampleDuration;
    if (flags & 0x100) {
      sampleDuration = MOZ_TRY(reader->ReadU32());
    }
    uint32_t sampleSize = mTfhd.mDefaultSampleSize;
    if (flags & 0x200) {
      sampleSize = MOZ_TRY(reader->ReadU32());
    }
    uint32_t sampleFlags = i ? mTfhd.mDefaultSampleFlags : firstSampleFlags;
    if (flags & 0x400) {
      sampleFlags = MOZ_TRY(reader->ReadU32());
    }
    int32_t ctsOffset = 0;
    if (flags & 0x800) {
      ctsOffset = MOZ_TRY(reader->Read32());
    }

    if (sampleSize) {
      Sample sample;
      auto byteRange = MakeMediaByteRangeChecked(offset, sampleSize);
      if (byteRange.isErr()) {
        LOG_WARN(Moof, "Invalid sample offset");
        return Err(NS_ERROR_FAILURE);
      }
      sample.mByteRange = byteRange.unwrap();
      offset += sampleSize;

      TimeUnit decodeOffset =
          MOZ_TRY(aMdhd.ToTimeUnit(decodeTime - aEdts.mMediaStart));
      TimeUnit emptyOffset = MOZ_TRY(aMvhd.ToTimeUnit(aEdts.mEmptyOffset));
      sample.mDecodeTime = decodeOffset + emptyOffset;
      TimeUnit startCts =
          MOZ_TRY(aMdhd.ToTimeUnit(decodeTime + ctsOffset - aEdts.mMediaStart));
      TimeUnit endCts = MOZ_TRY(aMdhd.ToTimeUnit(
          decodeTime + ctsOffset + sampleDuration - aEdts.mMediaStart));
      sample.mCompositionRange =
          MP4Interval<TimeUnit>(startCts + emptyOffset, endCts + emptyOffset);
      // Sometimes audio streams don't properly mark their samples as keyframes,
      // because every audio sample is a keyframe.
      sample.mSync = !(sampleFlags & 0x1010000) || aIsAudio;

      MOZ_ALWAYS_TRUE(mIndex.AppendElement(sample, fallible));

      mMdatRange = mMdatRange.Span(sample.mByteRange);
    }
    decodeTime += sampleDuration;
  }
  TimeUnit roundTime = MOZ_TRY(aMdhd.ToTimeUnit(sampleCount));
  mMaxRoundingError = roundTime + mMaxRoundingError;

  if (!decodeTime.isValid()) {
    LOG_WARN(Moof, "Decode time overflow in ParseTrun");
    return Err(NS_ERROR_FAILURE);
  }
  // The int64_t -> uint64_t conversion preserves the bit pattern; the next
  // ParseTrun call reinterprets it back via static_cast<int64_t>, recovering
  // the correct (possibly negative) decode time.
  *aDecodeTime = decodeTime.value();

  LOG_DEBUG(Trun, "Done.");
  return Ok();
}

Tkhd::Tkhd(const Box& aBox) : mTrackId(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Tkhd, "Parse failed");
  }
}

Result<Ok, nsresult> Tkhd::Parse(const Box& aBox) {
  BoxReader reader(aBox);
  uint32_t flags = MOZ_TRY(reader->ReadU32());
  uint8_t version = flags >> 24;
  if (version == 0) {
    uint32_t creationTime = MOZ_TRY(reader->ReadU32());
    uint32_t modificationTime = MOZ_TRY(reader->ReadU32());
    mTrackId = MOZ_TRY(reader->ReadU32());
    [[maybe_unused]] uint32_t reserved = MOZ_TRY(reader->ReadU32());
    uint32_t duration = MOZ_TRY(reader->ReadU32());
    NS_ASSERTION(!reserved, "reserved should be 0");

    mCreationTime = creationTime;
    mModificationTime = modificationTime;
    mDuration = duration;
  } else if (version == 1) {
    mCreationTime = MOZ_TRY(reader->ReadU64());
    mModificationTime = MOZ_TRY(reader->ReadU64());
    mTrackId = MOZ_TRY(reader->ReadU32());
    [[maybe_unused]] uint32_t reserved = MOZ_TRY(reader->ReadU32());
    NS_ASSERTION(!reserved, "reserved should be 0");
    mDuration = MOZ_TRY(reader->ReadU64());
  }
  return Ok();
}

Mvhd::Mvhd(const Box& aBox)
    : mCreationTime(0), mModificationTime(0), mTimescale(0), mDuration(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Mvhd, "Parse failed");
  }
}

Result<Ok, nsresult> Mvhd::Parse(const Box& aBox) {
  BoxReader reader(aBox);

  uint32_t flags = MOZ_TRY(reader->ReadU32());
  uint8_t version = flags >> 24;

  if (version == 0) {
    uint32_t creationTime = MOZ_TRY(reader->ReadU32());
    uint32_t modificationTime = MOZ_TRY(reader->ReadU32());
    mTimescale = MOZ_TRY(reader->ReadU32());
    uint32_t duration = MOZ_TRY(reader->ReadU32());
    mCreationTime = creationTime;
    mModificationTime = modificationTime;
    mDuration = duration;
  } else if (version == 1) {
    mCreationTime = MOZ_TRY(reader->ReadU64());
    mModificationTime = MOZ_TRY(reader->ReadU64());
    mTimescale = MOZ_TRY(reader->ReadU32());
    mDuration = MOZ_TRY(reader->ReadU64());
  } else {
    return Err(NS_ERROR_FAILURE);
  }
  return Ok();
}

Mdhd::Mdhd(const Box& aBox) : Mvhd(aBox) {}

Trex::Trex(const Box& aBox)
    : mFlags(0),
      mTrackId(0),
      mDefaultSampleDescriptionIndex(0),
      mDefaultSampleDuration(0),
      mDefaultSampleSize(0),
      mDefaultSampleFlags(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Trex, "Parse failed");
  }
}

Result<Ok, nsresult> Trex::Parse(const Box& aBox) {
  BoxReader reader(aBox);

  mFlags = MOZ_TRY(reader->ReadU32());
  mTrackId = MOZ_TRY(reader->ReadU32());
  mDefaultSampleDescriptionIndex = MOZ_TRY(reader->ReadU32());
  mDefaultSampleDuration = MOZ_TRY(reader->ReadU32());
  mDefaultSampleSize = MOZ_TRY(reader->ReadU32());
  mDefaultSampleFlags = MOZ_TRY(reader->ReadU32());

  return Ok();
}

Tfhd::Tfhd(const Box& aBox, const Trex& aTrex)
    : Trex(aTrex), mBaseDataOffset(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Tfhd, "Parse failed");
  }
}

Result<Ok, nsresult> Tfhd::Parse(const Box& aBox) {
  MOZ_ASSERT(aBox.IsType("tfhd"));
  MOZ_ASSERT(aBox.Parent()->IsType("traf"));
  MOZ_ASSERT(aBox.Parent()->Parent()->IsType("moof"));

  BoxReader reader(aBox);

  mFlags = MOZ_TRY(reader->ReadU32());
  mTrackId = MOZ_TRY(reader->ReadU32());
  mBaseDataOffset = aBox.Parent()->Parent()->Offset();
  if (mFlags & 0x01) {
    mBaseDataOffset = MOZ_TRY(reader->ReadU64());
  }
  if (mFlags & 0x02) {
    mDefaultSampleDescriptionIndex = MOZ_TRY(reader->ReadU32());
  }
  if (mFlags & 0x08) {
    mDefaultSampleDuration = MOZ_TRY(reader->ReadU32());
  }
  if (mFlags & 0x10) {
    mDefaultSampleSize = MOZ_TRY(reader->ReadU32());
  }
  if (mFlags & 0x20) {
    mDefaultSampleFlags = MOZ_TRY(reader->ReadU32());
  }

  return Ok();
}

Tfdt::Tfdt(const Box& aBox) : mBaseMediaDecodeTime(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Tfdt, "Parse failed");
  }
}

Result<Ok, nsresult> Tfdt::Parse(const Box& aBox) {
  BoxReader reader(aBox);

  uint32_t flags = MOZ_TRY(reader->ReadU32());
  uint8_t version = flags >> 24;
  if (version == 0) {
    mBaseMediaDecodeTime = MOZ_TRY(reader->ReadU32());
  } else if (version == 1) {
    mBaseMediaDecodeTime = MOZ_TRY(reader->ReadU64());
  }
  return Ok();
}

Edts::Edts(const Box& aBox) : mMediaStart(0), mEmptyOffset(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Edts, "Parse failed");
  }
}

Result<Ok, nsresult> Edts::Parse(const Box& aBox) {
  Box child = aBox.FirstChild();
  if (!child.IsType("elst")) {
    return Err(NS_ERROR_FAILURE);
  }

  BoxReader reader(child);
  uint32_t flags = MOZ_TRY(reader->ReadU32());
  uint8_t version = flags >> 24;
  bool emptyEntry = false;
  uint32_t entryCount = MOZ_TRY(reader->ReadU32());
  for (uint32_t i = 0; i < entryCount; i++) {
    uint64_t segment_duration;
    int64_t media_time;
    if (version == 1) {
      segment_duration = MOZ_TRY(reader->ReadU64());
      media_time = MOZ_TRY(reader->Read64());
    } else {
      segment_duration = MOZ_TRY(reader->ReadU32());
      media_time = MOZ_TRY(reader->Read32());
    }
    if (media_time == -1 && i) {
      LOG_WARN(Edts, "Multiple empty edit, not handled");
    } else if (media_time == -1) {
      if (segment_duration > std::numeric_limits<int64_t>::max()) {
        NS_WARNING("Segment duration higher than int64_t max.");
        mEmptyOffset = std::numeric_limits<int64_t>::max();
      } else {
        mEmptyOffset = static_cast<int64_t>(segment_duration);
      }
      emptyEntry = true;
    } else if (i > 1 || (i > 0 && !emptyEntry)) {
      LOG_WARN(Edts,
               "More than one edit entry, not handled. A/V sync will be wrong");
      break;
    } else {
      mMediaStart = media_time;
    }
    MOZ_TRY(reader->ReadU32());  // media_rate_integer and media_rate_fraction
  }

  return Ok();
}

Saiz::Saiz(const Box& aBox, AtomType aDefaultType)
    : mAuxInfoType(aDefaultType), mAuxInfoTypeParameter(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Saiz, "Parse failed");
  }
}

Result<Ok, nsresult> Saiz::Parse(const Box& aBox) {
  BoxReader reader(aBox);

  uint32_t flags = MOZ_TRY(reader->ReadU32());
  if (flags & 1) {
    mAuxInfoType = MOZ_TRY(reader->ReadU32());
    mAuxInfoTypeParameter = MOZ_TRY(reader->ReadU32());
  }
  uint8_t defaultSampleInfoSize = MOZ_TRY(reader->ReadU8());
  uint32_t count = MOZ_TRY(reader->ReadU32());
  if (defaultSampleInfoSize) {
    if (!mSampleInfoSize.SetLength(count, fallible)) {
      LOG_ERROR(Saiz, "OOM");
      return Err(NS_ERROR_FAILURE);
    }
    memset(mSampleInfoSize.Elements(), defaultSampleInfoSize,
           mSampleInfoSize.Length());
  } else {
    if (!reader->ReadArray(mSampleInfoSize, count)) {
      LOG_WARN(Saiz, "Incomplete Box (OOM or missing count:{})", count);
      return Err(NS_ERROR_FAILURE);
    }
  }
  return Ok();
}

Saio::Saio(const Box& aBox, AtomType aDefaultType)
    : mAuxInfoType(aDefaultType), mAuxInfoTypeParameter(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Saio, "Parse failed");
  }
}

Result<Ok, nsresult> Saio::Parse(const Box& aBox) {
  BoxReader reader(aBox);

  uint32_t flags = MOZ_TRY(reader->ReadU32());
  uint8_t version = flags >> 24;
  if (flags & 1) {
    mAuxInfoType = MOZ_TRY(reader->ReadU32());
    mAuxInfoTypeParameter = MOZ_TRY(reader->ReadU32());
  }

  size_t count = MOZ_TRY(reader->ReadU32());
  if (!mOffsets.SetCapacity(count, fallible)) {
    LOG_ERROR(Saiz, "OOM");
    return Err(NS_ERROR_FAILURE);
  }
  if (version == 0) {
    for (size_t i = 0; i < count; i++) {
      uint32_t offset = MOZ_TRY(reader->ReadU32());
      MOZ_ALWAYS_TRUE(mOffsets.AppendElement(offset, fallible));
    }
  } else {
    for (size_t i = 0; i < count; i++) {
      uint64_t offset = MOZ_TRY(reader->ReadU64());
      MOZ_ALWAYS_TRUE(mOffsets.AppendElement(offset, fallible));
    }
  }
  return Ok();
}

Sbgp::Sbgp(const Box& aBox) : mGroupingTypeParam(0) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Sbgp, "Parse failed");
  }
}

Result<Ok, nsresult> Sbgp::Parse(const Box& aBox) {
  BoxReader reader(aBox);

  uint32_t flags = MOZ_TRY(reader->ReadU32());
  const uint8_t version = flags >> 24;

  mGroupingType = MOZ_TRY(reader->ReadU32());

  if (version == 1) {
    mGroupingTypeParam = MOZ_TRY(reader->ReadU32());
  }

  uint32_t count = MOZ_TRY(reader->ReadU32());

  for (uint32_t i = 0; i < count; i++) {
    uint32_t sampleCount = MOZ_TRY(reader->ReadU32());
    uint32_t groupDescriptionIndex = MOZ_TRY(reader->ReadU32());

    SampleToGroupEntry entry(sampleCount, groupDescriptionIndex);
    if (!mEntries.AppendElement(entry, mozilla::fallible)) {
      LOG_ERROR(Sbgp, "OOM");
      return Err(NS_ERROR_FAILURE);
    }
  }
  return Ok();
}

Sgpd::Sgpd(const Box& aBox) {
  mValid = Parse(aBox).isOk();
  if (!mValid) {
    LOG_WARN(Sgpd, "Parse failed");
  }
}

Result<Ok, nsresult> Sgpd::Parse(const Box& aBox) {
  BoxReader reader(aBox);

  uint32_t flags = MOZ_TRY(reader->ReadU32());
  const uint8_t version = flags >> 24;

  mGroupingType = MOZ_TRY(reader->ReadU32());

  const uint32_t entrySize = sizeof(uint32_t) + kKeyIdSize;
  uint32_t defaultLength = 0;

  if (version == 1) {
    defaultLength = MOZ_TRY(reader->ReadU32());
    if (defaultLength < entrySize && defaultLength != 0) {
      return Err(NS_ERROR_FAILURE);
    }
  }

  uint32_t count = MOZ_TRY(reader->ReadU32());

  for (uint32_t i = 0; i < count; ++i) {
    if (version == 1 && defaultLength == 0) {
      uint32_t descriptionLength = MOZ_TRY(reader->ReadU32());
      if (descriptionLength < entrySize) {
        return Err(NS_ERROR_FAILURE);
      }
    }

    CencSampleEncryptionInfoEntry entry;
    bool valid = entry.Init(reader).isOk();
    if (!valid) {
      return Err(NS_ERROR_FAILURE);
    }
    if (!mEntries.AppendElement(entry, mozilla::fallible)) {
      LOG_ERROR(Sgpd, "OOM");
      return Err(NS_ERROR_FAILURE);
    }
  }
  return Ok();
}

Result<Ok, nsresult> CencSampleEncryptionInfoEntry::Init(BoxReader& aReader) {
  // Skip a reserved byte.
  MOZ_TRY(aReader->ReadU8());

  uint8_t pattern = MOZ_TRY(aReader->ReadU8());
  mCryptByteBlock = pattern >> 4;
  mSkipByteBlock = pattern & 0x0f;

  uint8_t isEncrypted = MOZ_TRY(aReader->ReadU8());
  mIsEncrypted = isEncrypted != 0;

  mIVSize = MOZ_TRY(aReader->ReadU8());

  // Read the key id.
  if (!mKeyId.SetLength(kKeyIdSize, fallible)) {
    LOG_ERROR(CencSampleEncryptionInfoEntry, "OOM");
    return Err(NS_ERROR_FAILURE);
  }
  for (uint32_t i = 0; i < kKeyIdSize; ++i) {
    mKeyId.ElementAt(i) = MOZ_TRY(aReader->ReadU8());
  }

  if (mIsEncrypted) {
    if (mIVSize != 8 && mIVSize != 16) {
      return Err(NS_ERROR_FAILURE);
    }
  } else if (mIVSize != 0) {
    // Protected content with 0 sized IV indicates a constant IV is present.
    // This is used for the cbcs scheme.
    uint8_t constantIVSize = MOZ_TRY(aReader->ReadU8());
    if (constantIVSize != 8 && constantIVSize != 16) {
      LOG_WARN(CencSampleEncryptionInfoEntry, "Unexpected constantIVSize: {}",
               constantIVSize);
      return Err(NS_ERROR_FAILURE);
    }
    if (!mConsantIV.SetLength(constantIVSize, mozilla::fallible)) {
      LOG_ERROR(CencSampleEncryptionInfoEntry, "OOM");
      return Err(NS_ERROR_FAILURE);
    }
    for (uint32_t i = 0; i < constantIVSize; ++i) {
      mConsantIV.ElementAt(i) = MOZ_TRY(aReader->ReadU8());
    }
  }

  return Ok();
}
}  // namespace mozilla

#undef LOG_DEBUG
#undef LOG_WARN
#undef LOG_ERROR

Messung V0.5 in Prozent
C=92 H=90 G=90

¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet am  2026-08-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.