/* 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 "AnnexB.h"

#include "BufferReader.h"
#include "ByteWriter.h"
#include "H264.h"
#include "H265.h"
#include "MediaData.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/Try.h"

mozilla::LazyLogModule gAnnexB("AnnexB");

#define LOG(msg, ...) MOZ_LOG_FMT(gAnnexB, LogLevel::Debug, msg, ##__VA_ARGS__)
#define LOGV(msg, ...) \
  MOZ_LOG_FMT(gAnnexB, LogLevel::Verbose, msg, ##__VA_ARGS__)

namespace mozilla {

static const uint8_t kAnnexBDelimiter[] = {0, 0, 0, 1};

/* static */
Result<Ok, nsresult> AnnexB::ConvertAVCCSampleToAnnexB(
    mozilla::MediaRawData* aSample, bool aAddSPS) {
  MOZ_ASSERT(aSample);

  if (!IsAVCC(aSample)) {
    return Ok();
  }
  MOZ_ASSERT(aSample->Data());

  MOZ_TRY(ConvertAVCCTo4BytesAVCC(aSample));

  if (aSample->Size() < 4) {
    // Nothing to do, it's corrupted anyway.
    return Ok();
  }

  BufferReader reader(aSample->Data(), aSample->Size());

  nsTArray<uint8_t> tmp;
  ByteWriter<BigEndian> writer(tmp);

  while (reader.Remaining() >= 4) {
    uint32_t nalLen = MOZ_TRY(reader.ReadU32());
    const uint8_t* p = reader.Read(nalLen);

    if (!writer.Write(kAnnexBDelimiter, std::size(kAnnexBDelimiter))) {
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }
    if (!p) {
      break;
    }
    if (!writer.Write(p, nalLen)) {
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }
  }

  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());

  if (!samplewriter->Replace(tmp.Elements(), tmp.Length())) {
    return Err(NS_ERROR_OUT_OF_MEMORY);
  }

  // Prepend the Annex B NAL with SPS and PPS tables to keyframes.
  // TODO: Avoid appending duplicate SPS and PPS NALUs. See bug 1961082.
  if (aAddSPS && aSample->mKeyframe) {
    RefPtr<MediaByteBuffer> annexB =
        ConvertAVCCExtraDataToAnnexB(aSample->mExtraData);
    if (!samplewriter->Prepend(annexB->Elements(), annexB->Length())) {
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }

    // Prepending the NAL with SPS/PPS will mess up the encryption subsample
    // offsets. So we need to account for the extra bytes by increasing
    // the length of the first clear data subsample. Otherwise decryption
    // will fail.
    if (aSample->mCrypto.IsEncrypted()) {
      if (aSample->mCrypto.mPlainSizes.Length() == 0) {
        CheckedUint32 plainSize{annexB->Length()};
        CheckedUint32 encryptedSize{samplewriter->Size()};
        encryptedSize -= annexB->Length();
        samplewriter->mCrypto.mPlainSizes.AppendElement(plainSize.value());
        samplewriter->mCrypto.mEncryptedSizes.AppendElement(
            encryptedSize.value());
      } else {
        CheckedUint32 newSize{samplewriter->mCrypto.mPlainSizes[0]};
        newSize += annexB->Length();
        samplewriter->mCrypto.mPlainSizes[0] = newSize.value();
      }
    }
    LOG("Appended extradata {} bytes", annexB->Length());
  }

  return Ok();
}

/* static */
Result<Ok, nsresult> AnnexB::ConvertHVCCSampleToAnnexB(
    mozilla::MediaRawData* aSample, bool aAddSPS) {
  MOZ_ASSERT(aSample);
  if (!IsHVCC(aSample)) {
    LOG("Not HVCC?");
    return Ok();
  }
  MOZ_ASSERT(aSample->Data());

  MOZ_TRY(ConvertHVCCTo4BytesHVCC(aSample));
  if (aSample->Size() < 4) {
    // Nothing to do, it's corrupted anyway.
    LOG("Corrupted HVCC sample?");
    return Ok();
  }

  BufferReader reader(aSample->Data(), aSample->Size());
  nsTArray<uint8_t> tmp;
  ByteWriter<BigEndian> writer(tmp);
  while (reader.Remaining() >= 4) {
    uint32_t nalLen = MOZ_TRY(reader.ReadU32());
    const uint8_t* p = reader.Read(nalLen);
    if (!writer.Write(kAnnexBDelimiter, std::size(kAnnexBDelimiter))) {
      LOG("Failed to write kAnnexBDelimiter, OOM?");
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }
    if (!p) {
      break;
    }
    if (!writer.Write(p, nalLen)) {
      LOG("Failed to write nalu, OOM?");
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }
  }

  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
  if (!samplewriter->Replace(tmp.Elements(), tmp.Length())) {
    LOG("Failed to write sample, OOM?");
    return Err(NS_ERROR_OUT_OF_MEMORY);
  }

  // Prepend the Annex B NAL with SPS and PPS tables to keyframes.
  // TODO: Avoid appending duplicate SPS and PPS NALUs. See bug 1961082.
  if (aAddSPS && aSample->mKeyframe) {
    RefPtr<MediaByteBuffer> annexB =
        ConvertHVCCExtraDataToAnnexB(aSample->mExtraData);
    if (!annexB) {
      LOG("Failed to convert HVCC extradata to AnnexB");
      return Err(NS_ERROR_FAILURE);
    }
    if (!samplewriter->Prepend(annexB->Elements(), annexB->Length())) {
      LOG("Failed to append annexB extradata");
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }

    // Prepending the NAL with SPS/PPS will mess up the encryption subsample
    // offsets. So we need to account for the extra bytes by increasing
    // the length of the first clear data subsample. Otherwise decryption
    // will fail.
    if (aSample->mCrypto.IsEncrypted()) {
      if (aSample->mCrypto.mPlainSizes.Length() == 0) {
        CheckedUint32 plainSize{annexB->Length()};
        CheckedUint32 encryptedSize{samplewriter->Size()};
        encryptedSize -= annexB->Length();
        samplewriter->mCrypto.mPlainSizes.AppendElement(plainSize.value());
        samplewriter->mCrypto.mEncryptedSizes.AppendElement(
            encryptedSize.value());
      } else {
        CheckedUint32 newSize{samplewriter->mCrypto.mPlainSizes[0]};
        newSize += annexB->Length();
        samplewriter->mCrypto.mPlainSizes[0] = newSize.value();
      }
    }
    LOG("Appended extradata {} bytes", annexB->Length());
  }
  return Ok();
}

already_AddRefed<mozilla::MediaByteBuffer> AnnexB::ConvertAVCCExtraDataToAnnexB(
    const mozilla::MediaByteBuffer* aExtraData,
    size_t* aSPSLength /* = nullptr */) {
  // AVCC 6 byte header looks like:
  //     +------+------+------+------+------+------+------+------+
  // [0] |   0  |   0  |   0  |   0  |   0  |   0  |   0  |   1  |
  //     +------+------+------+------+------+------+------+------+
  // [1] | profile                                               |
  //     +------+------+------+------+------+------+------+------+
  // [2] | compatiblity                                          |
  //     +------+------+------+------+------+------+------+------+
  // [3] | level                                                 |
  //     +------+------+------+------+------+------+------+------+
  // [4] | unused                                  | nalLenSiz-1 |
  //     +------+------+------+------+------+------+------+------+
  // [5] | unused             | numSps                           |
  //     +------+------+------+------+------+------+------+------+

  RefPtr<mozilla::MediaByteBuffer> annexB = new mozilla::MediaByteBuffer;

  BufferReader reader(*aExtraData);
  const uint8_t* ptr = reader.Read(5);
  if (ptr && ptr[0] == 1) {
    // Append SPS then PPS
    (void)reader.ReadU8().map(
        [&](uint8_t x) { return ConvertSPSOrPPS(reader, x & 31, annexB); });
    if (aSPSLength) {
      *aSPSLength = annexB->Length();
    }
    (void)reader.ReadU8().map(
        [&](uint8_t x) { return ConvertSPSOrPPS(reader, x, annexB); });
    // MP4Box adds extra bytes that we ignore. I don't know what they do.
  }

  return annexB.forget();
}

already_AddRefed<mozilla::MediaByteBuffer> AnnexB::ConvertHVCCExtraDataToAnnexB(
    const mozilla::MediaByteBuffer* aExtraData) {
  auto rv = HVCCConfig::Parse(aExtraData);
  if (rv.isErr()) {
    return nullptr;
  }
  const HVCCConfig hvcc = rv.unwrap();
  RefPtr<mozilla::MediaByteBuffer> annexB = new mozilla::MediaByteBuffer;
  for (const auto& nalu : hvcc.mNALUs) {
#ifdef MOZ_WMF
    if (nalu.mNalUnitType == H265NALU::NAL_TYPES::PREFIX_SEI_NUT) {
      RefPtr<MediaByteBuffer> filteredNalu =
          H265::FilterPrefixSEIForWindows(nalu);
      if (!filteredNalu) {
        LOGV(
            "Skipping PREFIX_SEI_NUT (size={}) from AnnexB extradata on "
            "Windows because it only contains unregistered user data",
            nalu.mNALU.Length());
        continue;
      }
      annexB->AppendElements(kAnnexBDelimiter, std::size(kAnnexBDelimiter));
      annexB->AppendElements(filteredNalu->Elements(), filteredNalu->Length());
      LOGV(
          "Insert filtered PREFIX_SEI_NUT (old size={}, new size={}) to "
          "AnnexB (size={})",
          nalu.mNALU.Length(), filteredNalu->Length(), annexB->Length());
      continue;
    }
#endif
    annexB->AppendElements(kAnnexBDelimiter, std::size(kAnnexBDelimiter));
    annexB->AppendElements(nalu.mNALU.Elements(), nalu.mNALU.Length());
    LOGV("Insert NALU (type={}, size={}) to AnnexB (size={})",
         nalu.mNalUnitType, nalu.mNALU.Length(), annexB->Length());
  }
  return annexB.forget();
}

Result<mozilla::Ok, nsresult> AnnexB::ConvertSPSOrPPS(
    BufferReader& aReader, uint8_t aCount, mozilla::MediaByteBuffer* aAnnexB) {
  for (int i = 0; i < aCount; i++) {
    uint16_t length = MOZ_TRY(aReader.ReadU16());

    const uint8_t* ptr = aReader.Read(length);
    if (!ptr) {
      return Err(NS_ERROR_FAILURE);
    }
    aAnnexB->AppendElements(kAnnexBDelimiter, std::size(kAnnexBDelimiter));
    aAnnexB->AppendElements(ptr, length);
  }
  return Ok();
}

static Result<Ok, nsresult> FindStartCodeInternal(BufferReader& aBr) {
  size_t offset = aBr.Offset();

  for (uint32_t i = 0; i < aBr.Align() && aBr.Remaining() >= 3; i++) {
    auto res = aBr.PeekU24();
    if (res.isOk() && (res.unwrap() == 0x000001)) {
      return Ok();
    }
    (void)aBr.Read(1);
  }

  while (aBr.Remaining() >= 6) {
    uint32_t x32 = MOZ_TRY(aBr.PeekU32());
    if ((x32 - 0x01010101) & (~x32) & 0x80808080) {  // Has 0x00 byte(s).
      if ((x32 >> 8) == 0x000001) {                  // 0x000001??
        return Ok();
      }
      if ((x32 & 0xffffff) == 0x000001) {  // 0x??000001
        (void)aBr.Read(1);
        return Ok();
      }
      if ((x32 & 0xff) == 0) {  // 0x??????00
        const uint8_t* p = aBr.Peek(1);
        if ((x32 & 0xff00) == 0 && p[4] == 1) {  // 0x????0000,01
          (void)aBr.Read(2);
          return Ok();
        }
        if (p[4] == 0 && p[5] == 1) {  // 0x??????00,00,01
          (void)aBr.Read(3);
          return Ok();
        }
      }
    }
    (void)aBr.Read(4);
  }

  while (aBr.Remaining() >= 3) {
    uint32_t data = MOZ_TRY(aBr.PeekU24());
    if (data == 0x000001) {
      return Ok();
    }
    (void)aBr.Read(1);
  }

  // No start code were found; Go back to the beginning.
  (void)aBr.Seek(offset);
  return Err(NS_ERROR_FAILURE);
}

static Result<Ok, nsresult> FindStartCode(BufferReader& aBr,
                                          size_t& aStartSize) {
  if (FindStartCodeInternal(aBr).isErr()) {
    aStartSize = 0;
    return Err(NS_ERROR_FAILURE);
  }

  aStartSize = 3;
  if (aBr.Offset()) {
    // Check if it's 4-bytes start code
    aBr.Rewind(1);
    uint8_t data = MOZ_TRY(aBr.ReadU8());
    if (data == 0) {
      aStartSize = 4;
    }
  }
  (void)aBr.Read(3);
  return Ok();
}

/* static */
void AnnexB::ParseNALEntries(const Span<const uint8_t>& aSpan,
                             nsTArray<AnnexB::NALEntry>& aEntries) {
  BufferReader reader(aSpan.data(), aSpan.Length());
  size_t startSize;
  auto rv = FindStartCode(reader, startSize);
  size_t startOffset = reader.Offset();
  if (rv.isOk()) {
    while (FindStartCode(reader, startSize).isOk()) {
      int64_t offset = reader.Offset();
      int64_t sizeNAL = offset - startOffset - startSize;
      aEntries.AppendElement(AnnexB::NALEntry(startOffset, sizeNAL));
      reader.Seek(startOffset);
      reader.Read(sizeNAL + startSize);
      startOffset = offset;
    }
  }
  int64_t sizeNAL = reader.Remaining();
  if (sizeNAL) {
    aEntries.AppendElement(AnnexB::NALEntry(startOffset, sizeNAL));
  }
}

/* static */
size_t AnnexB::FindNalType(const Span<const uint8_t>& aSpan,
                           const nsTArray<AnnexB::NALEntry>& aNalEntries,
                           NAL_TYPES aType, size_t aStartIndex) {
  for (size_t i = aStartIndex; i < aNalEntries.Length(); ++i) {
    uint8_t nalUnitType = aSpan[aNalEntries[i].mOffset] & 0x1f;
    if (nalUnitType == aType) {
      return i;
    }
  }
  return SIZE_MAX;
}

/* static */
bool AnnexB::FindAllNalTypes(const Span<const uint8_t>& aSpan,
                             const nsTArray<NAL_TYPES>& aTypes) {
  nsTArray<AnnexB::NALEntry> nalEntries;
  AnnexB::ParseNALEntries(aSpan, nalEntries);
  nsTArray<NAL_TYPES> checkTypes = aTypes.Clone();
  auto entry = nalEntries.cbegin();
  while (entry != nalEntries.cend()) {
    int64_t offset = (*entry).mOffset;
    uint8_t nalUnitType = (aSpan.cbegin() + offset)[0] & 0x1f;
    size_t index = checkTypes.IndexOf(nalUnitType);
    if (index != nsTArray<NAL_TYPES>::NoIndex) {
      checkTypes.RemoveElementAt(index);
      if (checkTypes.IsEmpty()) {
        return true;
      }
    }
    entry++;
  }
  return false;
}

static Result<mozilla::Ok, nsresult> ParseNALUnits(ByteWriter<BigEndian>& aBw,
                                                   BufferReader& aBr) {
  size_t startSize;

  auto rv = FindStartCode(aBr, startSize);
  if (rv.isOk()) {
    size_t startOffset = aBr.Offset();
    while (FindStartCode(aBr, startSize).isOk()) {
      size_t offset = aBr.Offset();
      size_t sizeNAL = offset - startOffset - startSize;
      aBr.Seek(startOffset);
      if (!aBw.WriteU32(sizeNAL) || !aBw.Write(aBr.Read(sizeNAL), sizeNAL)) {
        return Err(NS_ERROR_OUT_OF_MEMORY);
      }
      aBr.Read(startSize);
      startOffset = offset;
    }
  }
  size_t sizeNAL = aBr.Remaining();
  if (sizeNAL) {
    if (!aBw.WriteU32(sizeNAL) || !aBw.Write(aBr.Read(sizeNAL), sizeNAL)) {
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }
  }
  return Ok();
}

/* static */
RefPtr<MediaByteBuffer> AnnexB::ExtractExtraData(
    const Span<const uint8_t>& aSpan) {
  if (!IsAnnexB(aSpan)) {
    return nullptr;
  }

  nsTArray<NALEntry> paramSets;
  ParseNALEntries(aSpan, paramSets);

  size_t spsIndex =
      FindNalType(aSpan, paramSets, H264_NAL_SPS, /* aStartIndex */ 0);
  if (spsIndex == SIZE_MAX) {
    return nullptr;
  }

  size_t ppsIndex =
      FindNalType(aSpan, paramSets, H264_NAL_PPS, /* aStartIndex */ 0);
  if (ppsIndex == SIZE_MAX) {
    return nullptr;
  }

  auto annexb = MakeRefPtr<MediaByteBuffer>();
  const auto& spsEntry = paramSets.ElementAt(spsIndex);
  const auto& ppsEntry = paramSets.ElementAt(ppsIndex);
  const auto sps = aSpan.Subspan(spsEntry.mOffset, spsEntry.mSize);
  const auto pps = aSpan.Subspan(ppsEntry.mOffset, ppsEntry.mSize);
  annexb->AppendElements(kAnnexBDelimiter, std::size(kAnnexBDelimiter));
  annexb->AppendElements(sps);
  annexb->AppendElements(kAnnexBDelimiter, std::size(kAnnexBDelimiter));
  annexb->AppendElements(pps);
  return annexb;
}

/* static */
RefPtr<MediaByteBuffer> AnnexB::ExtractExtraDataForAVCC(
    const Span<const uint8_t>& aSpan) {
  if (!IsAnnexB(aSpan)) {
    return nullptr;
  }

  nsTArray<NALEntry> paramSets;
  ParseNALEntries(aSpan, paramSets);

  size_t spsIndex =
      FindNalType(aSpan, paramSets, H264_NAL_SPS, /* aStartIndex */ 0);
  if (spsIndex == SIZE_MAX) {
    return nullptr;
  }

  size_t ppsIndex =
      FindNalType(aSpan, paramSets, H264_NAL_PPS, /* aStartIndex */ 0);
  if (ppsIndex == SIZE_MAX) {
    return nullptr;
  }

  auto avcc = MakeRefPtr<MediaByteBuffer>();
  const auto& spsEntry = paramSets.ElementAt(spsIndex);
  const auto& ppsEntry = paramSets.ElementAt(ppsIndex);
  const auto sps = aSpan.Subspan(spsEntry.mOffset, spsEntry.mSize);
  const auto pps = aSpan.Subspan(ppsEntry.mOffset, ppsEntry.mSize);
  H264::WriteExtraData(avcc, sps[1], sps[2], sps[3], sps, pps);
  return avcc;
}

bool AnnexB::ConvertSampleToAVCC(mozilla::MediaRawData* aSample,
                                 const RefPtr<MediaByteBuffer>& aAVCCHeader) {
  if (IsAVCC(aSample)) {
    return ConvertAVCCTo4BytesAVCC(aSample).isOk();
  }
  if (!IsAnnexB(*aSample)) {
    // Not AnnexB, nothing to convert.
    return true;
  }

  nsTArray<uint8_t> nalu;
  ByteWriter<BigEndian> writer(nalu);
  BufferReader reader(aSample->Data(), aSample->Size());

  if (ParseNALUnits(writer, reader).isErr()) {
    return false;
  }
  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
  if (!samplewriter->Replace(nalu.Elements(), nalu.Length())) {
    return false;
  }

  if (aAVCCHeader) {
    aSample->mExtraData = aAVCCHeader;
    return true;
  }

  // Create the AVCC header.
  auto extradata = MakeRefPtr<mozilla::MediaByteBuffer>();
  static const uint8_t kFakeExtraData[] = {
      1 /* version */,
      0x64 /* profile (High) */,
      0 /* profile compat (0) */,
      40 /* level (40) */,
      0xfc | 3 /* nal size - 1 */,
      0xe0 /* num SPS (0) */,
      0 /* num PPS (0) */
  };
  // XXX(Bug 1631371) Check if this should use a fallible operation as it
  // pretended earlier.
  extradata->AppendElements(kFakeExtraData, std::size(kFakeExtraData));
  aSample->mExtraData = std::move(extradata);
  return true;
}

/* static */
Result<mozilla::Ok, nsresult> AnnexB::ConvertSampleToHVCC(
    mozilla::MediaRawData* aSample) {
  if (IsHVCC(aSample)) {
    return ConvertHVCCTo4BytesHVCC(aSample);
  }
  if (!IsAnnexB(*aSample)) {
    // Not AnnexB, nothing to convert.
    return Ok();
  }

  nsTArray<uint8_t> nalu;
  ByteWriter<BigEndian> writer(nalu);
  BufferReader reader(aSample->Data(), aSample->Size());
  if (auto rv = ParseNALUnits(writer, reader); rv.isErr()) {
    LOG("Failed fo parse AnnexB NALU for HVCC");
    return rv;
  }
  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
  if (!samplewriter->Replace(nalu.Elements(), nalu.Length())) {
    LOG("Failed fo replace NALU");
    return Err(NS_ERROR_OUT_OF_MEMORY);
  }
  if (aSample->mExtraData && HVCCConfig::Parse(aSample).isErr()) {
    LOG("Failed to parse invalid hvcc extradata");
    return Err(NS_ERROR_DOM_MEDIA_METADATA_ERR);
  }

  // Create the HEVC header as what we did for H264, which will later be
  // replaced with the one generated by MediaChangeMonitor.
  aSample->mExtraData = H265::CreateFakeExtraData();

  return Ok();
}

/* static */
Result<mozilla::Ok, nsresult> AnnexB::ConvertAVCCTo4BytesAVCC(
    mozilla::MediaRawData* aSample) {
  auto avcc = AVCCConfig::Parse(aSample);
  MOZ_ASSERT(avcc.isOk());
  return ConvertNALUTo4BytesNALU(aSample, avcc.unwrap().NALUSize());
}

/* static */
Result<mozilla::Ok, nsresult> AnnexB::ConvertHVCCTo4BytesHVCC(
    mozilla::MediaRawData* aSample) {
  auto hvcc = HVCCConfig::Parse(aSample);
  MOZ_ASSERT(hvcc.isOk());
  return ConvertNALUTo4BytesNALU(aSample, hvcc.unwrap().NALUSize());
}

/* static */
bool AnnexB::IsAVCC(const mozilla::MediaRawData* aSample) {
  return AVCCConfig::Parse(aSample).isOk();
}

/* static */
bool AnnexB::IsHVCC(const mozilla::MediaRawData* aSample) {
  return HVCCConfig::Parse(aSample).isOk();
}

/* static */
bool AnnexB::IsAnnexB(const Span<const uint8_t>& aSpan) {
  if (aSpan.Length() < 4) {
    return false;
  }
  uint32_t header = mozilla::BigEndian::readUint32(aSpan.Elements());
  return header == 0x00000001 || (header >> 8) == 0x000001;
}

/*  static */ mozilla::Result<mozilla::Ok, nsresult>
AnnexB::ConvertNALUTo4BytesNALU(mozilla::MediaRawData* aSample,
                                uint8_t aNALUSize) {
  // NALSize should be between 1 to 4.
  if (aNALUSize == 0 || aNALUSize > 4) {
    return Err(NS_ERROR_FAILURE);
  }

  // If the nalLenSize is already 4, we can only check if the data is corrupt
  // without replacing data in aSample.
  bool needConversion = aNALUSize != 4;

  MOZ_ASSERT(aSample);
  nsTArray<uint8_t> dest;
  ByteWriter<BigEndian> writer(dest);
  BufferReader reader(aSample->Data(), aSample->Size());
  while (reader.Remaining() > aNALUSize) {
    uint32_t nalLen;
    switch (aNALUSize) {
      case 1:
        nalLen = MOZ_TRY(reader.ReadU8());
        break;
      case 2:
        nalLen = MOZ_TRY(reader.ReadU16());
        break;
      case 3:
        nalLen = MOZ_TRY(reader.ReadU24());
        break;
      case 4:
        nalLen = MOZ_TRY(reader.ReadU32());
        break;
      default:
        MOZ_ASSERT_UNREACHABLE("Bytes of the NAL body length must be in [1,4]");
        return Err(NS_ERROR_ILLEGAL_VALUE);
    }
    const uint8_t* p = reader.Read(nalLen);
    if (!p) {
      // The data may be corrupt.
      return Err(NS_ERROR_UNEXPECTED);
    }
    if (!needConversion) {
      // We only parse aSample to see if it's corrupt.
      continue;
    }
    if (!writer.WriteU32(nalLen) || !writer.Write(p, nalLen)) {
      return Err(NS_ERROR_OUT_OF_MEMORY);
    }
  }
  if (!needConversion) {
    // We've parsed all the data, and it's all good.
    return Ok();
  }
  UniquePtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
  if (!samplewriter->Replace(dest.Elements(), dest.Length())) {
    return Err(NS_ERROR_OUT_OF_MEMORY);
  }
  return Ok();
}

#undef LOG
#undef LOGV

}  // namespace mozilla
