summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/binding
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/binding')
-rw-r--r--media/libstagefright/binding/Adts.cpp75
-rw-r--r--media/libstagefright/binding/AnnexB.cpp410
-rw-r--r--media/libstagefright/binding/BitReader.cpp104
-rw-r--r--media/libstagefright/binding/Box.cpp176
-rw-r--r--media/libstagefright/binding/BufferStream.cpp77
-rw-r--r--media/libstagefright/binding/DecoderData.cpp217
-rw-r--r--media/libstagefright/binding/H264.cpp528
-rw-r--r--media/libstagefright/binding/Index.cpp533
-rw-r--r--media/libstagefright/binding/MP4Metadata.cpp880
-rw-r--r--media/libstagefright/binding/MoofParser.cpp925
-rw-r--r--media/libstagefright/binding/ResourceStream.cpp67
-rw-r--r--media/libstagefright/binding/SinfParser.cpp72
-rw-r--r--media/libstagefright/binding/include/demuxer/TrackDemuxer.h34
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/Adts.h26
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/AnnexB.h55
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/Atom.h27
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/AtomType.h31
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/BitReader.h45
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/Box.h84
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/BufferStream.h46
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/ByteReader.h349
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/ByteWriter.h76
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/DecoderData.h93
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/H264.h372
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/Index.h128
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/Interval.h147
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/MP4Metadata.h54
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/MoofParser.h260
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/ResourceStream.h49
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/SinfParser.h51
-rw-r--r--media/libstagefright/binding/include/mp4_demuxer/Stream.h32
-rw-r--r--media/libstagefright/binding/include/mp4parse.h113
-rw-r--r--media/libstagefright/binding/mp4parse-cargo.patch45
-rw-r--r--media/libstagefright/binding/mp4parse/Cargo.toml29
-rw-r--r--media/libstagefright/binding/mp4parse/src/boxes.rs62
-rw-r--r--media/libstagefright/binding/mp4parse/src/lib.rs1704
-rw-r--r--media/libstagefright/binding/mp4parse/src/tests.rs860
-rw-r--r--media/libstagefright/binding/mp4parse/tests/afl.rs53
-rw-r--r--media/libstagefright/binding/mp4parse/tests/minimal.mp4bin0 -> 2591 bytes
-rw-r--r--media/libstagefright/binding/mp4parse/tests/public.rs97
-rw-r--r--media/libstagefright/binding/mp4parse_capi/Cargo.toml26
-rw-r--r--media/libstagefright/binding/mp4parse_capi/build.rs12
-rw-r--r--media/libstagefright/binding/mp4parse_capi/src/lib.rs870
-rwxr-xr-xmedia/libstagefright/binding/update-rust.sh56
44 files changed, 9950 insertions, 0 deletions
diff --git a/media/libstagefright/binding/Adts.cpp b/media/libstagefright/binding/Adts.cpp
new file mode 100644
index 000000000..4146ff008
--- /dev/null
+++ b/media/libstagefright/binding/Adts.cpp
@@ -0,0 +1,75 @@
+/* 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 "mp4_demuxer/Adts.h"
+#include "MediaData.h"
+#include "mozilla/Array.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsAutoPtr.h"
+
+using namespace mozilla;
+
+namespace mp4_demuxer
+{
+
+int8_t
+Adts::GetFrequencyIndex(uint32_t aSamplesPerSecond)
+{
+ static const uint32_t freq_lookup[] = { 96000, 88200, 64000, 48000, 44100,
+ 32000, 24000, 22050, 16000, 12000,
+ 11025, 8000, 7350, 0};
+
+ int8_t i = 0;
+ while (freq_lookup[i] && aSamplesPerSecond < freq_lookup[i]) {
+ i++;
+ }
+
+ if (!freq_lookup[i]) {
+ return -1;
+ }
+
+ return i;
+}
+
+bool
+Adts::ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex,
+ int8_t aProfile, MediaRawData* aSample)
+{
+ static const int kADTSHeaderSize = 7;
+
+ size_t newSize = aSample->Size() + kADTSHeaderSize;
+
+ // ADTS header uses 13 bits for packet size.
+ if (newSize >= (1 << 13) || aChannelCount > 15 ||
+ aFrequencyIndex < 0 || aProfile < 1 || aProfile > 4) {
+ return false;
+ }
+
+ Array<uint8_t, kADTSHeaderSize> header;
+ header[0] = 0xff;
+ header[1] = 0xf1;
+ header[2] =
+ ((aProfile - 1) << 6) + (aFrequencyIndex << 2) + (aChannelCount >> 2);
+ header[3] = ((aChannelCount & 0x3) << 6) + (newSize >> 11);
+ header[4] = (newSize & 0x7ff) >> 3;
+ header[5] = ((newSize & 7) << 5) + 0x1f;
+ header[6] = 0xfc;
+
+ nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+ if (!writer->Prepend(&header[0], ArrayLength(header))) {
+ return false;
+ }
+
+ if (aSample->mCrypto.mValid) {
+ if (aSample->mCrypto.mPlainSizes.Length() == 0) {
+ writer->mCrypto.mPlainSizes.AppendElement(kADTSHeaderSize);
+ writer->mCrypto.mEncryptedSizes.AppendElement(aSample->Size() - kADTSHeaderSize);
+ } else {
+ writer->mCrypto.mPlainSizes[0] += kADTSHeaderSize;
+ }
+ }
+
+ return true;
+}
+}
diff --git a/media/libstagefright/binding/AnnexB.cpp b/media/libstagefright/binding/AnnexB.cpp
new file mode 100644
index 000000000..2ca355757
--- /dev/null
+++ b/media/libstagefright/binding/AnnexB.cpp
@@ -0,0 +1,410 @@
+/* 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 "mozilla/ArrayUtils.h"
+#include "mozilla/EndianUtils.h"
+#include "mp4_demuxer/AnnexB.h"
+#include "mp4_demuxer/ByteReader.h"
+#include "mp4_demuxer/ByteWriter.h"
+#include "MediaData.h"
+#include "nsAutoPtr.h"
+
+using namespace mozilla;
+
+namespace mp4_demuxer
+{
+
+static const uint8_t kAnnexBDelimiter[] = { 0, 0, 0, 1 };
+
+bool
+AnnexB::ConvertSampleToAnnexB(mozilla::MediaRawData* aSample, bool aAddSPS)
+{
+ MOZ_ASSERT(aSample);
+
+ if (!IsAVCC(aSample)) {
+ return true;
+ }
+ MOZ_ASSERT(aSample->Data());
+
+ if (!ConvertSampleTo4BytesAVCC(aSample)) {
+ return false;
+ }
+
+ if (aSample->Size() < 4) {
+ // Nothing to do, it's corrupted anyway.
+ return true;
+ }
+
+ ByteReader reader(aSample->Data(), aSample->Size());
+
+ mozilla::Vector<uint8_t> tmp;
+ ByteWriter writer(tmp);
+
+ while (reader.Remaining() >= 4) {
+ uint32_t nalLen = reader.ReadU32();
+ const uint8_t* p = reader.Read(nalLen);
+
+ if (!writer.Write(kAnnexBDelimiter, ArrayLength(kAnnexBDelimiter))) {
+ return false;
+ }
+ if (!p) {
+ break;
+ }
+ if (!writer.Write(p, nalLen)) {
+ return false;
+ }
+ }
+
+ nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
+
+ if (!samplewriter->Replace(tmp.begin(), tmp.length())) {
+ return false;
+ }
+
+ // Prepend the Annex B NAL with SPS and PPS tables to keyframes.
+ if (aAddSPS && aSample->mKeyframe) {
+ RefPtr<MediaByteBuffer> annexB =
+ ConvertExtraDataToAnnexB(aSample->mExtraData);
+ if (!samplewriter->Prepend(annexB->Elements(), annexB->Length())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+already_AddRefed<mozilla::MediaByteBuffer>
+AnnexB::ConvertExtraDataToAnnexB(const mozilla::MediaByteBuffer* aExtraData)
+{
+ // 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;
+
+ ByteReader reader(*aExtraData);
+ const uint8_t* ptr = reader.Read(5);
+ if (ptr && ptr[0] == 1) {
+ // Append SPS then PPS
+ ConvertSPSOrPPS(reader, reader.ReadU8() & 31, annexB);
+ ConvertSPSOrPPS(reader, reader.ReadU8(), annexB);
+
+ // MP4Box adds extra bytes that we ignore. I don't know what they do.
+ }
+
+ return annexB.forget();
+}
+
+void
+AnnexB::ConvertSPSOrPPS(ByteReader& aReader, uint8_t aCount,
+ mozilla::MediaByteBuffer* aAnnexB)
+{
+ for (int i = 0; i < aCount; i++) {
+ uint16_t length = aReader.ReadU16();
+
+ const uint8_t* ptr = aReader.Read(length);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ aAnnexB->AppendElements(kAnnexBDelimiter, ArrayLength(kAnnexBDelimiter));
+ aAnnexB->AppendElements(ptr, length);
+ }
+}
+
+static bool
+FindStartCodeInternal(ByteReader& aBr) {
+ size_t offset = aBr.Offset();
+
+ for (uint32_t i = 0; i < aBr.Align() && aBr.Remaining() >= 3; i++) {
+ if (aBr.PeekU24() == 0x000001) {
+ return true;
+ }
+ aBr.Read(1);
+ }
+
+ while (aBr.Remaining() >= 6) {
+ uint32_t x32 = aBr.PeekU32();
+ if ((x32 - 0x01010101) & (~x32) & 0x80808080) {
+ if ((x32 >> 8) == 0x000001) {
+ return true;
+ }
+ if (x32 == 0x000001) {
+ aBr.Read(1);
+ return true;
+ }
+ if ((x32 & 0xff) == 0) {
+ const uint8_t* p = aBr.Peek(1);
+ if ((x32 & 0xff00) == 0 && p[4] == 1) {
+ aBr.Read(2);
+ return true;
+ }
+ if (p[4] == 0 && p[5] == 1) {
+ aBr.Read(3);
+ return true;
+ }
+ }
+ }
+ aBr.Read(4);
+ }
+
+ while (aBr.Remaining() >= 3) {
+ if (aBr.PeekU24() == 0x000001) {
+ return true;
+ }
+ aBr.Read(1);
+ }
+
+ // No start code were found; Go back to the beginning.
+ aBr.Seek(offset);
+ return false;
+}
+
+static bool
+FindStartCode(ByteReader& aBr, size_t& aStartSize)
+{
+ if (!FindStartCodeInternal(aBr)) {
+ aStartSize = 0;
+ return false;
+ }
+
+ aStartSize = 3;
+ if (aBr.Offset()) {
+ // Check if it's 4-bytes start code
+ aBr.Rewind(1);
+ if (aBr.ReadU8() == 0) {
+ aStartSize = 4;
+ }
+ }
+ aBr.Read(3);
+ return true;
+}
+
+static bool
+ParseNALUnits(ByteWriter& aBw, ByteReader& aBr)
+{
+ size_t startSize;
+
+ bool rv = FindStartCode(aBr, startSize);
+ if (rv) {
+ size_t startOffset = aBr.Offset();
+ while (FindStartCode(aBr, startSize)) {
+ 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 false;
+ }
+ aBr.Read(startSize);
+ startOffset = offset;
+ }
+ }
+ size_t sizeNAL = aBr.Remaining();
+ if (sizeNAL) {
+ if (!aBw.WriteU32(sizeNAL)
+ || !aBw.Write(aBr.Read(sizeNAL), sizeNAL)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+AnnexB::ConvertSampleToAVCC(mozilla::MediaRawData* aSample)
+{
+ if (IsAVCC(aSample)) {
+ return ConvertSampleTo4BytesAVCC(aSample);
+ }
+ if (!IsAnnexB(aSample)) {
+ // Not AnnexB, nothing to convert.
+ return true;
+ }
+
+ mozilla::Vector<uint8_t> nalu;
+ ByteWriter writer(nalu);
+ ByteReader reader(aSample->Data(), aSample->Size());
+
+ if (!ParseNALUnits(writer, reader)) {
+ return false;
+ }
+ nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
+ return samplewriter->Replace(nalu.begin(), nalu.length());
+}
+
+already_AddRefed<mozilla::MediaByteBuffer>
+AnnexB::ExtractExtraData(const mozilla::MediaRawData* aSample)
+{
+ RefPtr<mozilla::MediaByteBuffer> extradata = new mozilla::MediaByteBuffer;
+ if (HasSPS(aSample->mExtraData)) {
+ // We already have an explicit extradata, re-use it.
+ extradata = aSample->mExtraData;
+ return extradata.forget();
+ }
+
+ if (IsAnnexB(aSample)) {
+ // We can't extract data from AnnexB.
+ return extradata.forget();
+ }
+
+ // SPS content
+ mozilla::Vector<uint8_t> sps;
+ ByteWriter spsw(sps);
+ int numSps = 0;
+ // PPS content
+ mozilla::Vector<uint8_t> pps;
+ ByteWriter ppsw(pps);
+ int numPps = 0;
+
+ int nalLenSize;
+ if (IsAVCC(aSample)) {
+ nalLenSize = ((*aSample->mExtraData)[4] & 3) + 1;
+ } else {
+ // We do not have an extradata, assume it's AnnexB converted to AVCC via
+ // ConvertSampleToAVCC.
+ nalLenSize = 4;
+ }
+ ByteReader reader(aSample->Data(), aSample->Size());
+
+ // Find SPS and PPS NALUs in AVCC data
+ while (reader.Remaining() > nalLenSize) {
+ uint32_t nalLen;
+ switch (nalLenSize) {
+ case 1: nalLen = reader.ReadU8(); break;
+ case 2: nalLen = reader.ReadU16(); break;
+ case 3: nalLen = reader.ReadU24(); break;
+ case 4: nalLen = reader.ReadU32(); break;
+ }
+ uint8_t nalType = reader.PeekU8() & 0x1f;
+ const uint8_t* p = reader.Read(nalLen);
+ if (!p) {
+ return extradata.forget();
+ }
+
+ if (nalType == 0x7) { /* SPS */
+ numSps++;
+ if (!spsw.WriteU16(nalLen)
+ || !spsw.Write(p, nalLen)) {
+ return extradata.forget();
+ }
+ } else if (nalType == 0x8) { /* PPS */
+ numPps++;
+ if (!ppsw.WriteU16(nalLen)
+ || !ppsw.Write(p, nalLen)) {
+ return extradata.forget();
+ }
+ }
+ }
+
+ if (numSps && sps.length() > 5) {
+ extradata->AppendElement(1); // version
+ extradata->AppendElement(sps[3]); // profile
+ extradata->AppendElement(sps[4]); // profile compat
+ extradata->AppendElement(sps[5]); // level
+ extradata->AppendElement(0xfc | 3); // nal size - 1
+ extradata->AppendElement(0xe0 | numSps);
+ extradata->AppendElements(sps.begin(), sps.length());
+ extradata->AppendElement(numPps);
+ if (numPps) {
+ extradata->AppendElements(pps.begin(), pps.length());
+ }
+ }
+
+ return extradata.forget();
+}
+
+bool
+AnnexB::HasSPS(const mozilla::MediaRawData* aSample)
+{
+ return HasSPS(aSample->mExtraData);
+}
+
+bool
+AnnexB::HasSPS(const mozilla::MediaByteBuffer* aExtraData)
+{
+ if (!aExtraData) {
+ return false;
+ }
+
+ ByteReader reader(aExtraData);
+ const uint8_t* ptr = reader.Read(5);
+ if (!ptr || !reader.CanRead8()) {
+ return false;
+ }
+ uint8_t numSps = reader.ReadU8() & 0x1f;
+
+ return numSps > 0;
+}
+
+bool
+AnnexB::ConvertSampleTo4BytesAVCC(mozilla::MediaRawData* aSample)
+{
+ MOZ_ASSERT(IsAVCC(aSample));
+
+ int nalLenSize = ((*aSample->mExtraData)[4] & 3) + 1;
+
+ if (nalLenSize == 4) {
+ return true;
+ }
+ mozilla::Vector<uint8_t> dest;
+ ByteWriter writer(dest);
+ ByteReader reader(aSample->Data(), aSample->Size());
+ while (reader.Remaining() > nalLenSize) {
+ uint32_t nalLen;
+ switch (nalLenSize) {
+ case 1: nalLen = reader.ReadU8(); break;
+ case 2: nalLen = reader.ReadU16(); break;
+ case 3: nalLen = reader.ReadU24(); break;
+ case 4: nalLen = reader.ReadU32(); break;
+ }
+ const uint8_t* p = reader.Read(nalLen);
+ if (!p) {
+ return true;
+ }
+ if (!writer.WriteU32(nalLen)
+ || !writer.Write(p, nalLen)) {
+ return false;
+ }
+ }
+ nsAutoPtr<MediaRawDataWriter> samplewriter(aSample->CreateWriter());
+ return samplewriter->Replace(dest.begin(), dest.length());
+}
+
+bool
+AnnexB::IsAVCC(const mozilla::MediaRawData* aSample)
+{
+ return aSample->Size() >= 3 && aSample->mExtraData &&
+ aSample->mExtraData->Length() >= 7 && (*aSample->mExtraData)[0] == 1;
+}
+
+bool
+AnnexB::IsAnnexB(const mozilla::MediaRawData* aSample)
+{
+ if (aSample->Size() < 4) {
+ return false;
+ }
+ uint32_t header = mozilla::BigEndian::readUint32(aSample->Data());
+ return header == 0x00000001 || (header >> 8) == 0x000001;
+}
+
+bool
+AnnexB::CompareExtraData(const mozilla::MediaByteBuffer* aExtraData1,
+ const mozilla::MediaByteBuffer* aExtraData2)
+{
+ // Very crude comparison.
+ return aExtraData1 == aExtraData2 || *aExtraData1 == *aExtraData2;
+}
+
+} // namespace mp4_demuxer
diff --git a/media/libstagefright/binding/BitReader.cpp b/media/libstagefright/binding/BitReader.cpp
new file mode 100644
index 000000000..4bf80caba
--- /dev/null
+++ b/media/libstagefright/binding/BitReader.cpp
@@ -0,0 +1,104 @@
+/* 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 "mp4_demuxer/BitReader.h"
+#include <media/stagefright/foundation/ABitReader.h>
+
+using namespace mozilla;
+using namespace stagefright;
+
+namespace mp4_demuxer
+{
+
+BitReader::BitReader(const mozilla::MediaByteBuffer* aBuffer)
+ : mBitReader(new ABitReader(aBuffer->Elements(), aBuffer->Length()))
+ , mSize(aBuffer->Length()) {}
+
+BitReader::BitReader(const uint8_t* aBuffer, size_t aLength)
+ : mBitReader(new ABitReader(aBuffer, aLength))
+ , mSize(aLength) {}
+
+BitReader::~BitReader() {}
+
+uint32_t
+BitReader::ReadBits(size_t aNum)
+{
+ MOZ_ASSERT(aNum <= 32);
+ if (mBitReader->numBitsLeft() < aNum) {
+ return 0;
+ }
+ return mBitReader->getBits(aNum);
+}
+
+// Read unsigned integer Exp-Golomb-coded.
+uint32_t
+BitReader::ReadUE()
+{
+ uint32_t i = 0;
+
+ while (ReadBit() == 0 && i < 32) {
+ i++;
+ }
+ if (i == 32) {
+ // This can happen if the data is invalid, or if it's
+ // short, since ReadBit() will return 0 when it runs
+ // off the end of the buffer.
+ NS_WARNING("Invalid H.264 data");
+ return 0;
+ }
+ uint32_t r = ReadBits(i);
+ r += (1 << i) - 1;
+ return r;
+}
+
+// Read signed integer Exp-Golomb-coded.
+int32_t
+BitReader::ReadSE()
+{
+ int32_t r = ReadUE();
+ if (r & 1) {
+ return (r+1) / 2;
+ } else {
+ return -r / 2;
+ }
+}
+
+uint64_t
+BitReader::ReadU64()
+{
+ uint64_t hi = ReadU32();
+ uint32_t lo = ReadU32();
+ return (hi << 32) | lo;
+}
+
+uint64_t
+BitReader::ReadUTF8()
+{
+ int64_t val = ReadBits(8);
+ uint32_t top = (val & 0x80) >> 1;
+
+ if ((val & 0xc0) == 0x80 || val >= 0xFE) {
+ // error.
+ return -1;
+ }
+ while (val & top) {
+ int tmp = ReadBits(8) - 128;
+ if (tmp >> 6) {
+ // error.
+ return -1;
+ }
+ val = (val << 6) + tmp;
+ top <<= 5;
+ }
+ val &= (top << 1) - 1;
+ return val;
+}
+
+size_t
+BitReader::BitCount() const
+{
+ return mSize * 8 - mBitReader->numBitsLeft();
+}
+
+} // namespace mp4_demuxer
diff --git a/media/libstagefright/binding/Box.cpp b/media/libstagefright/binding/Box.cpp
new file mode 100644
index 000000000..4bef10816
--- /dev/null
+++ b/media/libstagefright/binding/Box.cpp
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mp4_demuxer/Box.h"
+#include "mp4_demuxer/Stream.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Unused.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+namespace mp4_demuxer {
+
+// Limit reads to 32MiB max.
+// static
+const uint64_t Box::kMAX_BOX_READ = 32 * 1024 * 1024;
+
+// Returns the offset from the start of the body of a box of type |aType|
+// to the start of its first child.
+static uint32_t
+BoxOffset(AtomType aType)
+{
+ const uint32_t FULLBOX_OFFSET = 4;
+
+ if (aType == AtomType("mp4a") || aType == AtomType("enca")) {
+ // AudioSampleEntry; ISO 14496-12, section 8.16
+ return 28;
+ } else if (aType == AtomType("mp4v") || aType == AtomType("encv")) {
+ // VideoSampleEntry; ISO 14496-12, section 8.16
+ return 78;
+ } else if (aType == AtomType("stsd")) {
+ // SampleDescriptionBox; ISO 14496-12, section 8.16
+ // This is a FullBox, and contains a |count| member before its child
+ // boxes.
+ return FULLBOX_OFFSET + 4;
+ }
+
+ return 0;
+}
+
+Box::Box(BoxContext* aContext, uint64_t aOffset, const Box* aParent)
+ : mContext(aContext), mParent(aParent)
+{
+ uint8_t header[8];
+
+ if (aOffset > INT64_MAX - sizeof(header)) {
+ return;
+ }
+
+ MediaByteRange headerRange(aOffset, aOffset + sizeof(header));
+ if (mParent && !mParent->mRange.Contains(headerRange)) {
+ return;
+ }
+
+ const MediaByteRange* byteRange;
+ for (int i = 0; ; i++) {
+ if (i == mContext->mByteRanges.Length()) {
+ return;
+ }
+
+ byteRange = static_cast<const MediaByteRange*>(&mContext->mByteRanges[i]);
+ if (byteRange->Contains(headerRange)) {
+ break;
+ }
+ }
+
+ size_t bytes;
+ if (!mContext->mSource->CachedReadAt(aOffset, header, sizeof(header),
+ &bytes) ||
+ bytes != sizeof(header)) {
+ return;
+ }
+
+ uint64_t size = BigEndian::readUint32(header);
+ if (size == 1) {
+ uint8_t bigLength[8];
+ if (aOffset > INT64_MAX - sizeof(header) - sizeof(bigLength)) {
+ return;
+ }
+ MediaByteRange bigLengthRange(headerRange.mEnd,
+ headerRange.mEnd + sizeof(bigLength));
+ if ((mParent && !mParent->mRange.Contains(bigLengthRange)) ||
+ !byteRange->Contains(bigLengthRange) ||
+ !mContext->mSource->CachedReadAt(aOffset + sizeof(header), bigLength,
+ sizeof(bigLength), &bytes) ||
+ bytes != sizeof(bigLength)) {
+ return;
+ }
+ size = BigEndian::readUint64(bigLength);
+ mBodyOffset = bigLengthRange.mEnd;
+ } else if (size == 0) {
+ // box extends to end of file.
+ size = mContext->mByteRanges.LastInterval().mEnd - aOffset;
+ mBodyOffset = headerRange.mEnd;
+ } else {
+ mBodyOffset = headerRange.mEnd;
+ }
+
+ if (size > INT64_MAX) {
+ return;
+ }
+ int64_t end = static_cast<int64_t>(aOffset) + static_cast<int64_t>(size);
+ if (end < static_cast<int64_t>(aOffset)) {
+ // Overflowed.
+ return;
+ }
+
+ mType = BigEndian::readUint32(&header[4]);
+ mChildOffset = mBodyOffset + BoxOffset(mType);
+
+ MediaByteRange boxRange(aOffset, end);
+ if (mChildOffset > boxRange.mEnd ||
+ (mParent && !mParent->mRange.Contains(boxRange)) ||
+ !byteRange->Contains(boxRange)) {
+ return;
+ }
+
+ mRange = boxRange;
+}
+
+Box::Box()
+ : mContext(nullptr)
+{}
+
+Box
+Box::Next() const
+{
+ MOZ_ASSERT(IsAvailable());
+ return Box(mContext, mRange.mEnd, mParent);
+}
+
+Box
+Box::FirstChild() const
+{
+ MOZ_ASSERT(IsAvailable());
+ if (mChildOffset == mRange.mEnd) {
+ return Box();
+ }
+ return Box(mContext, mChildOffset, this);
+}
+
+nsTArray<uint8_t>
+Box::Read()
+{
+ nsTArray<uint8_t> out;
+ Unused << Read(&out, mRange);
+ return out;
+}
+
+bool
+Box::Read(nsTArray<uint8_t>* aDest, const MediaByteRange& aRange)
+{
+ int64_t length;
+ if (!mContext->mSource->Length(&length)) {
+ // The HTTP server didn't give us a length to work with.
+ // Limit the read to kMAX_BOX_READ max.
+ length = std::min(aRange.mEnd - mChildOffset, kMAX_BOX_READ);
+ } else {
+ length = aRange.mEnd - mChildOffset;
+ }
+ aDest->SetLength(length);
+ size_t bytes;
+ if (!mContext->mSource->CachedReadAt(mChildOffset, aDest->Elements(),
+ aDest->Length(), &bytes) ||
+ bytes != aDest->Length()) {
+ // Byte ranges are being reported incorrectly
+ NS_WARNING("Read failed in mp4_demuxer::Box::Read()");
+ aDest->Clear();
+ return false;
+ }
+ return true;
+}
+}
diff --git a/media/libstagefright/binding/BufferStream.cpp b/media/libstagefright/binding/BufferStream.cpp
new file mode 100644
index 000000000..6f6a58398
--- /dev/null
+++ b/media/libstagefright/binding/BufferStream.cpp
@@ -0,0 +1,77 @@
+/* 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 "mp4_demuxer/BufferStream.h"
+#include "MediaData.h"
+#include "MediaResource.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+namespace mp4_demuxer {
+
+BufferStream::BufferStream()
+ : mStartOffset(0)
+ , mData(new mozilla::MediaByteBuffer)
+{
+}
+
+BufferStream::BufferStream(mozilla::MediaByteBuffer* aBuffer)
+ : mStartOffset(0)
+ , mData(aBuffer)
+{
+}
+
+BufferStream::~BufferStream()
+{
+}
+
+/*virtual*/ bool
+BufferStream::ReadAt(int64_t aOffset, void* aData, size_t aLength,
+ size_t* aBytesRead)
+{
+ if (aOffset < mStartOffset || aOffset > mStartOffset + mData->Length()) {
+ return false;
+ }
+ *aBytesRead =
+ std::min(aLength, size_t(mStartOffset + mData->Length() - aOffset));
+ memcpy(aData, mData->Elements() + aOffset - mStartOffset, *aBytesRead);
+ return true;
+}
+
+/*virtual*/ bool
+BufferStream::CachedReadAt(int64_t aOffset, void* aData, size_t aLength,
+ size_t* aBytesRead)
+{
+ return ReadAt(aOffset, aData, aLength, aBytesRead);
+}
+
+/*virtual*/ bool
+BufferStream::Length(int64_t* aLength)
+{
+ *aLength = mStartOffset + mData->Length();
+ return true;
+}
+
+/* virtual */ void
+BufferStream::DiscardBefore(int64_t aOffset)
+{
+ if (aOffset > mStartOffset) {
+ mData->RemoveElementsAt(0, aOffset - mStartOffset);
+ mStartOffset = aOffset;
+ }
+}
+
+bool
+BufferStream::AppendBytes(const uint8_t* aData, size_t aLength)
+{
+ return mData->AppendElements(aData, aLength, fallible);
+}
+
+MediaByteRange
+BufferStream::GetByteRange()
+{
+ return MediaByteRange(mStartOffset, mStartOffset + mData->Length());
+}
+}
diff --git a/media/libstagefright/binding/DecoderData.cpp b/media/libstagefright/binding/DecoderData.cpp
new file mode 100644
index 000000000..aaf2fb32c
--- /dev/null
+++ b/media/libstagefright/binding/DecoderData.cpp
@@ -0,0 +1,217 @@
+/* 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 "mp4_demuxer/Adts.h"
+#include "mp4_demuxer/AnnexB.h"
+#include "mp4_demuxer/ByteReader.h"
+#include "mp4_demuxer/DecoderData.h"
+#include <media/stagefright/foundation/ABitReader.h>
+#include "media/stagefright/MetaData.h"
+#include "media/stagefright/MediaDefs.h"
+#include "media/stagefright/Utils.h"
+#include "mozilla/ArrayUtils.h"
+#include "include/ESDS.h"
+
+#ifdef MOZ_RUST_MP4PARSE
+#include "mp4parse.h"
+#endif
+
+using namespace stagefright;
+
+namespace mp4_demuxer
+{
+
+static int32_t
+FindInt32(const MetaData* mMetaData, uint32_t mKey)
+{
+ int32_t value;
+ if (!mMetaData->findInt32(mKey, &value))
+ return 0;
+ return value;
+}
+
+static int64_t
+FindInt64(const MetaData* mMetaData, uint32_t mKey)
+{
+ int64_t value;
+ if (!mMetaData->findInt64(mKey, &value))
+ return 0;
+ return value;
+}
+
+template <typename T, size_t N>
+static bool
+FindData(const MetaData* aMetaData, uint32_t aKey, mozilla::Vector<T, N>* aDest)
+{
+ const void* data;
+ size_t size;
+ uint32_t type;
+
+ aDest->clear();
+ // There's no point in checking that the type matches anything because it
+ // isn't set consistently in the MPEG4Extractor.
+ if (!aMetaData->findData(aKey, &type, &data, &size) || size % sizeof(T)) {
+ return false;
+ }
+
+ aDest->append(reinterpret_cast<const T*>(data), size / sizeof(T));
+ return true;
+}
+
+template <typename T>
+static bool
+FindData(const MetaData* aMetaData, uint32_t aKey, nsTArray<T>* aDest)
+{
+ const void* data;
+ size_t size;
+ uint32_t type;
+
+ aDest->Clear();
+ // There's no point in checking that the type matches anything because it
+ // isn't set consistently in the MPEG4Extractor.
+ if (!aMetaData->findData(aKey, &type, &data, &size) || size % sizeof(T)) {
+ return false;
+ }
+
+ aDest->AppendElements(reinterpret_cast<const T*>(data), size / sizeof(T));
+ return true;
+}
+
+static bool
+FindData(const MetaData* aMetaData, uint32_t aKey, mozilla::MediaByteBuffer* aDest)
+{
+ return FindData(aMetaData, aKey, static_cast<nsTArray<uint8_t>*>(aDest));
+}
+
+bool
+CryptoFile::DoUpdate(const uint8_t* aData, size_t aLength)
+{
+ ByteReader reader(aData, aLength);
+ while (reader.Remaining()) {
+ PsshInfo psshInfo;
+ if (!reader.ReadArray(psshInfo.uuid, 16)) {
+ return false;
+ }
+
+ if (!reader.CanReadType<uint32_t>()) {
+ return false;
+ }
+ auto length = reader.ReadType<uint32_t>();
+
+ if (!reader.ReadArray(psshInfo.data, length)) {
+ return false;
+ }
+ pssh.AppendElement(psshInfo);
+ }
+ return true;
+}
+
+static void
+UpdateTrackInfo(mozilla::TrackInfo& aConfig,
+ const MetaData* aMetaData,
+ const char* aMimeType)
+{
+ mozilla::CryptoTrack& crypto = aConfig.mCrypto;
+ aConfig.mMimeType = aMimeType;
+ aConfig.mDuration = FindInt64(aMetaData, kKeyDuration);
+ aConfig.mMediaTime = FindInt64(aMetaData, kKeyMediaTime);
+ aConfig.mTrackId = FindInt32(aMetaData, kKeyTrackID);
+ aConfig.mCrypto.mValid = aMetaData->findInt32(kKeyCryptoMode, &crypto.mMode) &&
+ aMetaData->findInt32(kKeyCryptoDefaultIVSize, &crypto.mIVSize) &&
+ FindData(aMetaData, kKeyCryptoKey, &crypto.mKeyId);
+}
+
+void
+MP4AudioInfo::Update(const MetaData* aMetaData,
+ const char* aMimeType)
+{
+ UpdateTrackInfo(*this, aMetaData, aMimeType);
+ mChannels = FindInt32(aMetaData, kKeyChannelCount);
+ mBitDepth = FindInt32(aMetaData, kKeySampleSize);
+ mRate = FindInt32(aMetaData, kKeySampleRate);
+ mProfile = FindInt32(aMetaData, kKeyAACProfile);
+
+ if (FindData(aMetaData, kKeyESDS, mExtraData)) {
+ ESDS esds(mExtraData->Elements(), mExtraData->Length());
+
+ const void* data;
+ size_t size;
+ if (esds.getCodecSpecificInfo(&data, &size) == OK) {
+ const uint8_t* cdata = reinterpret_cast<const uint8_t*>(data);
+ mCodecSpecificConfig->AppendElements(cdata, size);
+ if (size > 1) {
+ ABitReader br(cdata, size);
+ mExtendedProfile = br.getBits(5);
+
+ if (mExtendedProfile == 31) { // AAC-ELD => additional 6 bits
+ mExtendedProfile = 32 + br.getBits(6);
+ }
+ }
+ }
+ }
+}
+
+bool
+MP4AudioInfo::IsValid() const
+{
+ return mChannels > 0 && mRate > 0 &&
+ // Accept any mime type here, but if it's aac, validate the profile.
+ (!mMimeType.Equals(MEDIA_MIMETYPE_AUDIO_AAC) ||
+ mProfile > 0 || mExtendedProfile > 0);
+}
+
+void
+MP4VideoInfo::Update(const MetaData* aMetaData, const char* aMimeType)
+{
+ UpdateTrackInfo(*this, aMetaData, aMimeType);
+ mDisplay.width = FindInt32(aMetaData, kKeyDisplayWidth);
+ mDisplay.height = FindInt32(aMetaData, kKeyDisplayHeight);
+ mImage.width = FindInt32(aMetaData, kKeyWidth);
+ mImage.height = FindInt32(aMetaData, kKeyHeight);
+ mRotation = VideoInfo::ToSupportedRotation(FindInt32(aMetaData, kKeyRotation));
+
+ FindData(aMetaData, kKeyAVCC, mExtraData);
+ if (!mExtraData->Length()) {
+ if (FindData(aMetaData, kKeyESDS, mExtraData)) {
+ ESDS esds(mExtraData->Elements(), mExtraData->Length());
+
+ const void* data;
+ size_t size;
+ if (esds.getCodecSpecificInfo(&data, &size) == OK) {
+ const uint8_t* cdata = reinterpret_cast<const uint8_t*>(data);
+ mCodecSpecificConfig->AppendElements(cdata, size);
+ }
+ }
+ }
+
+}
+
+#ifdef MOZ_RUST_MP4PARSE
+void
+MP4VideoInfo::Update(const mp4parse_track_info* track,
+ const mp4parse_track_video_info* video)
+{
+ if (track->codec == MP4PARSE_CODEC_AVC) {
+ mMimeType = MEDIA_MIMETYPE_VIDEO_AVC;
+ } else if (track->codec == MP4PARSE_CODEC_VP9) {
+ mMimeType = NS_LITERAL_CSTRING("video/vp9");
+ }
+ mTrackId = track->track_id;
+ mDuration = track->duration;
+ mMediaTime = track->media_time;
+ mDisplay.width = video->display_width;
+ mDisplay.height = video->display_height;
+ mImage.width = video->image_width;
+ mImage.height = video->image_height;
+}
+#endif
+
+bool
+MP4VideoInfo::IsValid() const
+{
+ return (mDisplay.width > 0 && mDisplay.height > 0) ||
+ (mImage.width > 0 && mImage.height > 0);
+}
+
+}
diff --git a/media/libstagefright/binding/H264.cpp b/media/libstagefright/binding/H264.cpp
new file mode 100644
index 000000000..a34ae2d3a
--- /dev/null
+++ b/media/libstagefright/binding/H264.cpp
@@ -0,0 +1,528 @@
+/* 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 "mozilla/ArrayUtils.h"
+#include "mozilla/PodOperations.h"
+#include "mp4_demuxer/AnnexB.h"
+#include "mp4_demuxer/BitReader.h"
+#include "mp4_demuxer/ByteReader.h"
+#include "mp4_demuxer/ByteWriter.h"
+#include "mp4_demuxer/H264.h"
+#include <media/stagefright/foundation/ABitReader.h>
+#include <limits>
+
+using namespace mozilla;
+
+namespace mp4_demuxer
+{
+
+SPSData::SPSData()
+{
+ PodZero(this);
+ // Default values when they aren't defined as per ITU-T H.264 (2014/02).
+ chroma_format_idc = 1;
+ video_format = 5;
+ colour_primaries = 2;
+ transfer_characteristics = 2;
+ sample_ratio = 1.0;
+}
+
+/* static */ already_AddRefed<mozilla::MediaByteBuffer>
+H264::DecodeNALUnit(const mozilla::MediaByteBuffer* aNAL)
+{
+ MOZ_ASSERT(aNAL);
+
+ if (aNAL->Length() < 4) {
+ return nullptr;
+ }
+
+ RefPtr<mozilla::MediaByteBuffer> rbsp = new mozilla::MediaByteBuffer;
+ ByteReader reader(aNAL);
+ uint8_t nal_unit_type = reader.ReadU8() & 0x1f;
+ uint32_t nalUnitHeaderBytes = 1;
+ if (nal_unit_type == 14 || nal_unit_type == 20 || nal_unit_type == 21) {
+ bool svc_extension_flag = false;
+ bool avc_3d_extension_flag = false;
+ if (nal_unit_type != 21) {
+ svc_extension_flag = reader.PeekU8() & 0x80;
+ } else {
+ avc_3d_extension_flag = reader.PeekU8() & 0x80;
+ }
+ if (svc_extension_flag) {
+ nalUnitHeaderBytes += 3;
+ } else if (avc_3d_extension_flag) {
+ nalUnitHeaderBytes += 2;
+ } else {
+ nalUnitHeaderBytes += 3;
+ }
+ }
+ if (!reader.Read(nalUnitHeaderBytes - 1)) {
+ return nullptr;
+ }
+ uint32_t lastbytes = 0xffff;
+ while (reader.Remaining()) {
+ uint8_t byte = reader.ReadU8();
+ if ((lastbytes & 0xffff) == 0 && byte == 0x03) {
+ // reset last two bytes, to detect the 0x000003 sequence again.
+ lastbytes = 0xffff;
+ } else {
+ rbsp->AppendElement(byte);
+ }
+ lastbytes = (lastbytes << 8) | byte;
+ }
+ return rbsp.forget();
+}
+
+static int32_t
+ConditionDimension(float aValue)
+{
+ // This will exclude NaNs and too-big values.
+ if (aValue > 1.0 && aValue <= INT32_MAX)
+ return int32_t(aValue);
+ return 0;
+}
+
+/* static */ bool
+H264::DecodeSPS(const mozilla::MediaByteBuffer* aSPS, SPSData& aDest)
+{
+ if (!aSPS) {
+ return false;
+ }
+ BitReader br(aSPS);
+
+ int32_t lastScale;
+ int32_t nextScale;
+ int32_t deltaScale;
+
+ aDest.profile_idc = br.ReadBits(8);
+ aDest.constraint_set0_flag = br.ReadBit();
+ aDest.constraint_set1_flag = br.ReadBit();
+ aDest.constraint_set2_flag = br.ReadBit();
+ aDest.constraint_set3_flag = br.ReadBit();
+ aDest.constraint_set4_flag = br.ReadBit();
+ aDest.constraint_set5_flag = br.ReadBit();
+ br.ReadBits(2); // reserved_zero_2bits
+ aDest.level_idc = br.ReadBits(8);
+ aDest.seq_parameter_set_id = br.ReadUE();
+ if (aDest.profile_idc == 100 || aDest.profile_idc == 110 ||
+ aDest.profile_idc == 122 || aDest.profile_idc == 244 ||
+ aDest.profile_idc == 44 || aDest.profile_idc == 83 ||
+ aDest.profile_idc == 86 || aDest.profile_idc == 118 ||
+ aDest.profile_idc == 128 || aDest.profile_idc == 138 ||
+ aDest.profile_idc == 139 || aDest.profile_idc == 134) {
+ if ((aDest.chroma_format_idc = br.ReadUE()) == 3) {
+ aDest.separate_colour_plane_flag = br.ReadBit();
+ }
+ br.ReadUE(); // bit_depth_luma_minus8
+ br.ReadUE(); // bit_depth_chroma_minus8
+ br.ReadBit(); // qpprime_y_zero_transform_bypass_flag
+ if (br.ReadBit()) { // seq_scaling_matrix_present_flag
+ for (int idx = 0; idx < ((aDest.chroma_format_idc != 3) ? 8 : 12); ++idx) {
+ if (br.ReadBit()) { // Scaling list present
+ lastScale = nextScale = 8;
+ int sl_n = (idx < 6) ? 16 : 64;
+ for (int sl_i = 0; sl_i < sl_n; sl_i++) {
+ if (nextScale) {
+ deltaScale = br.ReadSE();
+ nextScale = (lastScale + deltaScale + 256) % 256;
+ }
+ lastScale = (nextScale == 0) ? lastScale : nextScale;
+ }
+ }
+ }
+ }
+ } else if (aDest.profile_idc == 183) {
+ aDest.chroma_format_idc = 0;
+ } else {
+ // default value if chroma_format_idc isn't set.
+ aDest.chroma_format_idc = 1;
+ }
+ aDest.log2_max_frame_num = br.ReadUE() + 4;
+ aDest.pic_order_cnt_type = br.ReadUE();
+ if (aDest.pic_order_cnt_type == 0) {
+ aDest.log2_max_pic_order_cnt_lsb = br.ReadUE() + 4;
+ } else if (aDest.pic_order_cnt_type == 1) {
+ aDest.delta_pic_order_always_zero_flag = br.ReadBit();
+ aDest.offset_for_non_ref_pic = br.ReadSE();
+ aDest.offset_for_top_to_bottom_field = br.ReadSE();
+ uint32_t num_ref_frames_in_pic_order_cnt_cycle = br.ReadUE();
+ for (uint32_t i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) {
+ br.ReadSE(); // offset_for_ref_frame[i]
+ }
+ }
+ aDest.max_num_ref_frames = br.ReadUE();
+ aDest.gaps_in_frame_num_allowed_flag = br.ReadBit();
+ aDest.pic_width_in_mbs = br.ReadUE() + 1;
+ aDest.pic_height_in_map_units = br.ReadUE() + 1;
+ aDest.frame_mbs_only_flag = br.ReadBit();
+ if (!aDest.frame_mbs_only_flag) {
+ aDest.pic_height_in_map_units *= 2;
+ aDest.mb_adaptive_frame_field_flag = br.ReadBit();
+ }
+ br.ReadBit(); // direct_8x8_inference_flag
+ aDest.frame_cropping_flag = br.ReadBit();
+ if (aDest.frame_cropping_flag) {
+ aDest.frame_crop_left_offset = br.ReadUE();
+ aDest.frame_crop_right_offset = br.ReadUE();
+ aDest.frame_crop_top_offset = br.ReadUE();
+ aDest.frame_crop_bottom_offset = br.ReadUE();
+ }
+
+ aDest.sample_ratio = 1.0f;
+ aDest.vui_parameters_present_flag = br.ReadBit();
+ if (aDest.vui_parameters_present_flag) {
+ vui_parameters(br, aDest);
+ }
+
+ // Calculate common values.
+
+ uint8_t ChromaArrayType =
+ aDest.separate_colour_plane_flag ? 0 : aDest.chroma_format_idc;
+ // Calculate width.
+ uint32_t CropUnitX = 1;
+ uint32_t SubWidthC = aDest.chroma_format_idc == 3 ? 1 : 2;
+ if (ChromaArrayType != 0) {
+ CropUnitX = SubWidthC;
+ }
+
+ // Calculate Height
+ uint32_t CropUnitY = 2 - aDest.frame_mbs_only_flag;
+ uint32_t SubHeightC = aDest.chroma_format_idc <= 1 ? 2 : 1;
+ if (ChromaArrayType != 0) {
+ CropUnitY *= SubHeightC;
+ }
+
+ uint32_t width = aDest.pic_width_in_mbs * 16;
+ uint32_t height = aDest.pic_height_in_map_units * 16;
+ if (aDest.frame_crop_left_offset <= std::numeric_limits<int32_t>::max() / 4 / CropUnitX &&
+ aDest.frame_crop_right_offset <= std::numeric_limits<int32_t>::max() / 4 / CropUnitX &&
+ aDest.frame_crop_top_offset <= std::numeric_limits<int32_t>::max() / 4 / CropUnitY &&
+ aDest.frame_crop_bottom_offset <= std::numeric_limits<int32_t>::max() / 4 / CropUnitY &&
+ (aDest.frame_crop_left_offset + aDest.frame_crop_right_offset) * CropUnitX < width &&
+ (aDest.frame_crop_top_offset + aDest.frame_crop_bottom_offset) * CropUnitY < height) {
+ aDest.crop_left = aDest.frame_crop_left_offset * CropUnitX;
+ aDest.crop_right = aDest.frame_crop_right_offset * CropUnitX;
+ aDest.crop_top = aDest.frame_crop_top_offset * CropUnitY;
+ aDest.crop_bottom = aDest.frame_crop_bottom_offset * CropUnitY;
+ } else {
+ // Nonsensical value, ignore them.
+ aDest.crop_left = aDest.crop_right = aDest.crop_top = aDest.crop_bottom = 0;
+ }
+
+ aDest.pic_width = width - aDest.crop_left - aDest.crop_right;
+ aDest.pic_height = height - aDest.crop_top - aDest.crop_bottom;
+
+ aDest.interlaced = !aDest.frame_mbs_only_flag;
+
+ // Determine display size.
+ if (aDest.sample_ratio > 1.0) {
+ // Increase the intrinsic width
+ aDest.display_width =
+ ConditionDimension(aDest.pic_width * aDest.sample_ratio);
+ aDest.display_height = aDest.pic_height;
+ } else {
+ // Increase the intrinsic height
+ aDest.display_width = aDest.pic_width;
+ aDest.display_height =
+ ConditionDimension(aDest.pic_height / aDest.sample_ratio);
+ }
+
+ return true;
+}
+
+/* static */ void
+H264::vui_parameters(BitReader& aBr, SPSData& aDest)
+{
+ aDest.aspect_ratio_info_present_flag = aBr.ReadBit();
+ if (aDest.aspect_ratio_info_present_flag) {
+ aDest.aspect_ratio_idc = aBr.ReadBits(8);
+ aDest.sar_width = aDest.sar_height = 0;
+
+ // From E.2.1 VUI parameters semantics (ITU-T H.264 02/2014)
+ switch (aDest.aspect_ratio_idc) {
+ case 0:
+ // Unspecified
+ break;
+ case 1:
+ /*
+ 1:1
+ 7680x4320 16:9 frame without horizontal overscan
+ 3840x2160 16:9 frame without horizontal overscan
+ 1280x720 16:9 frame without horizontal overscan
+ 1920x1080 16:9 frame without horizontal overscan (cropped from 1920x1088)
+ 640x480 4:3 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 1.0f;
+ break;
+ case 2:
+ /*
+ 12:11
+ 720x576 4:3 frame with horizontal overscan
+ 352x288 4:3 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 12.0 / 11.0;
+ break;
+ case 3:
+ /*
+ 10:11
+ 720x480 4:3 frame with horizontal overscan
+ 352x240 4:3 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 10.0 / 11.0;
+ break;
+ case 4:
+ /*
+ 16:11
+ 720x576 16:9 frame with horizontal overscan
+ 528x576 4:3 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 16.0 / 11.0;
+ break;
+ case 5:
+ /*
+ 40:33
+ 720x480 16:9 frame with horizontal overscan
+ 528x480 4:3 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 40.0 / 33.0;
+ break;
+ case 6:
+ /*
+ 24:11
+ 352x576 4:3 frame without horizontal overscan
+ 480x576 16:9 frame with horizontal overscan
+ */
+ aDest.sample_ratio = 24.0 / 11.0;
+ break;
+ case 7:
+ /*
+ 20:11
+ 352x480 4:3 frame without horizontal overscan
+ 480x480 16:9 frame with horizontal overscan
+ */
+ aDest.sample_ratio = 20.0 / 11.0;
+ break;
+ case 8:
+ /*
+ 32:11
+ 352x576 16:9 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 32.0 / 11.0;
+ break;
+ case 9:
+ /*
+ 80:33
+ 352x480 16:9 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 80.0 / 33.0;
+ break;
+ case 10:
+ /*
+ 18:11
+ 480x576 4:3 frame with horizontal overscan
+ */
+ aDest.sample_ratio = 18.0 / 11.0;
+ break;
+ case 11:
+ /*
+ 15:11
+ 480x480 4:3 frame with horizontal overscan
+ */
+ aDest.sample_ratio = 15.0 / 11.0;
+ break;
+ case 12:
+ /*
+ 64:33
+ 528x576 16:9 frame with horizontal overscan
+ */
+ aDest.sample_ratio = 64.0 / 33.0;
+ break;
+ case 13:
+ /*
+ 160:99
+ 528x480 16:9 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 160.0 / 99.0;
+ break;
+ case 14:
+ /*
+ 4:3
+ 1440x1080 16:9 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 4.0 / 3.0;
+ break;
+ case 15:
+ /*
+ 3:2
+ 1280x1080 16:9 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 3.2 / 2.0;
+ break;
+ case 16:
+ /*
+ 2:1
+ 960x1080 16:9 frame without horizontal overscan
+ */
+ aDest.sample_ratio = 2.0 / 1.0;
+ break;
+ case 255:
+ /* Extended_SAR */
+ aDest.sar_width = aBr.ReadBits(16);
+ aDest.sar_height = aBr.ReadBits(16);
+ if (aDest.sar_width && aDest.sar_height) {
+ aDest.sample_ratio = float(aDest.sar_width) / float(aDest.sar_height);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (aBr.ReadBit()) { //overscan_info_present_flag
+ aDest.overscan_appropriate_flag = aBr.ReadBit();
+ }
+
+ if (aBr.ReadBit()) { // video_signal_type_present_flag
+ aDest.video_format = aBr.ReadBits(3);
+ aDest.video_full_range_flag = aBr.ReadBit();
+ aDest.colour_description_present_flag = aBr.ReadBit();
+ if (aDest.colour_description_present_flag) {
+ aDest.colour_primaries = aBr.ReadBits(8);
+ aDest.transfer_characteristics = aBr.ReadBits(8);
+ aDest.matrix_coefficients = aBr.ReadBits(8);
+ }
+ }
+
+ aDest.chroma_loc_info_present_flag = aBr.ReadBit();
+ if (aDest.chroma_loc_info_present_flag) {
+ aDest.chroma_sample_loc_type_top_field = aBr.ReadUE();
+ aDest.chroma_sample_loc_type_bottom_field = aBr.ReadUE();
+ }
+
+ aDest.timing_info_present_flag = aBr.ReadBit();
+ if (aDest.timing_info_present_flag ) {
+ aDest.num_units_in_tick = aBr.ReadBits(32);
+ aDest.time_scale = aBr.ReadBits(32);
+ aDest.fixed_frame_rate_flag = aBr.ReadBit();
+ }
+}
+
+/* static */ bool
+H264::DecodeSPSFromExtraData(const mozilla::MediaByteBuffer* aExtraData, SPSData& aDest)
+{
+ if (!AnnexB::HasSPS(aExtraData)) {
+ return false;
+ }
+ ByteReader reader(aExtraData);
+
+ if (!reader.Read(5)) {
+ return false;
+ }
+
+ if (!(reader.ReadU8() & 0x1f)) {
+ // No SPS.
+ return false;
+ }
+ uint16_t length = reader.ReadU16();
+
+ if ((reader.PeekU8() & 0x1f) != 7) {
+ // Not a SPS NAL type.
+ return false;
+ }
+
+ const uint8_t* ptr = reader.Read(length);
+ if (!ptr) {
+ return false;
+ }
+
+ RefPtr<mozilla::MediaByteBuffer> rawNAL = new mozilla::MediaByteBuffer;
+ rawNAL->AppendElements(ptr, length);
+
+ RefPtr<mozilla::MediaByteBuffer> sps = DecodeNALUnit(rawNAL);
+
+ if (!sps) {
+ return false;
+ }
+
+ return DecodeSPS(sps, aDest);
+}
+
+/* static */ bool
+H264::EnsureSPSIsSane(SPSData& aSPS)
+{
+ bool valid = true;
+ static const float default_aspect = 4.0f / 3.0f;
+ if (aSPS.sample_ratio <= 0.0f || aSPS.sample_ratio > 6.0f) {
+ if (aSPS.pic_width && aSPS.pic_height) {
+ aSPS.sample_ratio =
+ (float) aSPS.pic_width / (float) aSPS.pic_height;
+ } else {
+ aSPS.sample_ratio = default_aspect;
+ }
+ aSPS.display_width = aSPS.pic_width;
+ aSPS.display_height = aSPS.pic_height;
+ valid = false;
+ }
+ if (aSPS.max_num_ref_frames > 16) {
+ aSPS.max_num_ref_frames = 16;
+ valid = false;
+ }
+ return valid;
+}
+
+/* static */ uint32_t
+H264::ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData)
+{
+ uint32_t maxRefFrames = 4;
+ // Retrieve video dimensions from H264 SPS NAL.
+ SPSData spsdata;
+ if (DecodeSPSFromExtraData(aExtraData, spsdata)) {
+ // max_num_ref_frames determines the size of the sliding window
+ // we need to queue that many frames in order to guarantee proper
+ // pts frames ordering. Use a minimum of 4 to ensure proper playback of
+ // non compliant videos.
+ maxRefFrames =
+ std::min(std::max(maxRefFrames, spsdata.max_num_ref_frames + 1), 16u);
+ }
+ return maxRefFrames;
+}
+
+/* static */ H264::FrameType
+H264::GetFrameType(const mozilla::MediaRawData* aSample)
+{
+ if (!AnnexB::IsAVCC(aSample)) {
+ // We must have a valid AVCC frame with extradata.
+ return FrameType::INVALID;
+ }
+ MOZ_ASSERT(aSample->Data());
+
+ int nalLenSize = ((*aSample->mExtraData)[4] & 3) + 1;
+
+ ByteReader reader(aSample->Data(), aSample->Size());
+
+ while (reader.Remaining() >= nalLenSize) {
+ uint32_t nalLen;
+ switch (nalLenSize) {
+ case 1: nalLen = reader.ReadU8(); break;
+ case 2: nalLen = reader.ReadU16(); break;
+ case 3: nalLen = reader.ReadU24(); break;
+ case 4: nalLen = reader.ReadU32(); break;
+ }
+ if (!nalLen) {
+ continue;
+ }
+ const uint8_t* p = reader.Read(nalLen);
+ if (!p) {
+ return FrameType::INVALID;
+ }
+ if ((p[0] & 0x1f) == 5) {
+ // IDR NAL.
+ return FrameType::I_FRAME;
+ }
+ }
+
+ return FrameType::OTHER;
+}
+
+} // namespace mp4_demuxer
diff --git a/media/libstagefright/binding/Index.cpp b/media/libstagefright/binding/Index.cpp
new file mode 100644
index 000000000..9d6ab5028
--- /dev/null
+++ b/media/libstagefright/binding/Index.cpp
@@ -0,0 +1,533 @@
+/* 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 "mp4_demuxer/ByteReader.h"
+#include "mp4_demuxer/Index.h"
+#include "mp4_demuxer/Interval.h"
+#include "mp4_demuxer/SinfParser.h"
+#include "nsAutoPtr.h"
+#include "mozilla/RefPtr.h"
+
+#include <algorithm>
+#include <limits>
+
+using namespace stagefright;
+using namespace mozilla;
+using namespace mozilla::media;
+
+namespace mp4_demuxer
+{
+
+class MOZ_STACK_CLASS RangeFinder
+{
+public:
+ // Given that we're processing this in order we don't use a binary search
+ // to find the apropriate time range. Instead we search linearly from the
+ // last used point.
+ explicit RangeFinder(const MediaByteRangeSet& ranges)
+ : mRanges(ranges), mIndex(0)
+ {
+ // Ranges must be normalised for this to work
+ }
+
+ bool Contains(MediaByteRange aByteRange);
+
+private:
+ const MediaByteRangeSet& mRanges;
+ size_t mIndex;
+};
+
+bool
+RangeFinder::Contains(MediaByteRange aByteRange)
+{
+ if (!mRanges.Length()) {
+ return false;
+ }
+
+ if (mRanges[mIndex].ContainsStrict(aByteRange)) {
+ return true;
+ }
+
+ if (aByteRange.mStart < mRanges[mIndex].mStart) {
+ // Search backwards
+ do {
+ if (!mIndex) {
+ return false;
+ }
+ --mIndex;
+ if (mRanges[mIndex].ContainsStrict(aByteRange)) {
+ return true;
+ }
+ } while (aByteRange.mStart < mRanges[mIndex].mStart);
+
+ return false;
+ }
+
+ while (aByteRange.mEnd > mRanges[mIndex].mEnd) {
+ if (mIndex == mRanges.Length() - 1) {
+ return false;
+ }
+ ++mIndex;
+ if (mRanges[mIndex].ContainsStrict(aByteRange)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+SampleIterator::SampleIterator(Index* aIndex)
+ : mIndex(aIndex)
+ , mCurrentMoof(0)
+ , mCurrentSample(0)
+{
+ mIndex->RegisterIterator(this);
+}
+
+SampleIterator::~SampleIterator()
+{
+ mIndex->UnregisterIterator(this);
+}
+
+already_AddRefed<MediaRawData> SampleIterator::GetNext()
+{
+ Sample* s(Get());
+ if (!s) {
+ return nullptr;
+ }
+
+ int64_t length = std::numeric_limits<int64_t>::max();
+ mIndex->mSource->Length(&length);
+ if (s->mByteRange.mEnd > length) {
+ // We don't have this complete sample.
+ return nullptr;
+ }
+
+ RefPtr<MediaRawData> sample = new MediaRawData();
+ sample->mTimecode= s->mDecodeTime;
+ sample->mTime = s->mCompositionRange.start;
+ sample->mDuration = s->mCompositionRange.Length();
+ sample->mOffset = s->mByteRange.mStart;
+ sample->mKeyframe = s->mSync;
+
+ nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
+ // Do the blocking read
+ if (!writer->SetSize(s->mByteRange.Length())) {
+ return nullptr;
+ }
+
+ size_t bytesRead;
+ if (!mIndex->mSource->ReadAt(sample->mOffset, writer->Data(), sample->Size(),
+ &bytesRead) || bytesRead != sample->Size()) {
+ return nullptr;
+ }
+
+ if (!s->mCencRange.IsEmpty()) {
+ MoofParser* parser = mIndex->mMoofParser.get();
+
+ if (!parser || !parser->mSinf.IsValid()) {
+ return nullptr;
+ }
+
+ uint8_t ivSize = parser->mSinf.mDefaultIVSize;
+
+ // The size comes from an 8 bit field
+ AutoTArray<uint8_t, 256> cenc;
+ cenc.SetLength(s->mCencRange.Length());
+ if (!mIndex->mSource->ReadAt(s->mCencRange.mStart, cenc.Elements(), cenc.Length(),
+ &bytesRead) || bytesRead != cenc.Length()) {
+ return nullptr;
+ }
+ ByteReader reader(cenc);
+ writer->mCrypto.mValid = true;
+ writer->mCrypto.mIVSize = ivSize;
+
+ if (!reader.ReadArray(writer->mCrypto.mIV, ivSize)) {
+ return nullptr;
+ }
+
+ if (reader.CanRead16()) {
+ uint16_t count = reader.ReadU16();
+
+ if (reader.Remaining() < count * 6) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < count; i++) {
+ writer->mCrypto.mPlainSizes.AppendElement(reader.ReadU16());
+ writer->mCrypto.mEncryptedSizes.AppendElement(reader.ReadU32());
+ }
+ } else {
+ // No subsample information means the entire sample is encrypted.
+ writer->mCrypto.mPlainSizes.AppendElement(0);
+ writer->mCrypto.mEncryptedSizes.AppendElement(sample->Size());
+ }
+ }
+
+ Next();
+
+ return sample.forget();
+}
+
+Sample* SampleIterator::Get()
+{
+ if (!mIndex->mMoofParser) {
+ MOZ_ASSERT(!mCurrentMoof);
+ return mCurrentSample < mIndex->mIndex.Length()
+ ? &mIndex->mIndex[mCurrentSample]
+ : nullptr;
+ }
+
+ nsTArray<Moof>& moofs = mIndex->mMoofParser->Moofs();
+ while (true) {
+ if (mCurrentMoof == moofs.Length()) {
+ if (!mIndex->mMoofParser->BlockingReadNextMoof()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(mCurrentMoof < moofs.Length());
+ }
+ if (mCurrentSample < moofs[mCurrentMoof].mIndex.Length()) {
+ break;
+ }
+ mCurrentSample = 0;
+ ++mCurrentMoof;
+ }
+ return &moofs[mCurrentMoof].mIndex[mCurrentSample];
+}
+
+void SampleIterator::Next()
+{
+ ++mCurrentSample;
+}
+
+void SampleIterator::Seek(Microseconds aTime)
+{
+ size_t syncMoof = 0;
+ size_t syncSample = 0;
+ mCurrentMoof = 0;
+ mCurrentSample = 0;
+ Sample* sample;
+ while (!!(sample = Get())) {
+ if (sample->mCompositionRange.start > aTime) {
+ break;
+ }
+ if (sample->mSync) {
+ syncMoof = mCurrentMoof;
+ syncSample = mCurrentSample;
+ }
+ if (sample->mCompositionRange.start == aTime) {
+ break;
+ }
+ Next();
+ }
+ mCurrentMoof = syncMoof;
+ mCurrentSample = syncSample;
+}
+
+Microseconds
+SampleIterator::GetNextKeyframeTime()
+{
+ SampleIterator itr(*this);
+ Sample* sample;
+ while (!!(sample = itr.Get())) {
+ if (sample->mSync) {
+ return sample->mCompositionRange.start;
+ }
+ itr.Next();
+ }
+ return -1;
+}
+
+Index::Index(const nsTArray<Indice>& aIndex,
+ Stream* aSource,
+ uint32_t aTrackId,
+ bool aIsAudio)
+ : mSource(aSource)
+ , mIsAudio(aIsAudio)
+{
+ if (aIndex.IsEmpty()) {
+ mMoofParser = new MoofParser(aSource, aTrackId, aIsAudio);
+ } else {
+ if (!mIndex.SetCapacity(aIndex.Length(), fallible)) {
+ // OOM.
+ return;
+ }
+ media::IntervalSet<int64_t> intervalTime;
+ MediaByteRange intervalRange;
+ bool haveSync = false;
+ bool progressive = true;
+ int64_t lastOffset = 0;
+ for (size_t i = 0; i < aIndex.Length(); i++) {
+ const Indice& indice = aIndex[i];
+ if (indice.sync || mIsAudio) {
+ haveSync = true;
+ }
+ if (!haveSync) {
+ continue;
+ }
+
+ Sample sample;
+ sample.mByteRange = MediaByteRange(indice.start_offset,
+ indice.end_offset);
+ sample.mCompositionRange = Interval<Microseconds>(indice.start_composition,
+ indice.end_composition);
+ sample.mDecodeTime = indice.start_decode;
+ sample.mSync = indice.sync || mIsAudio;
+ // FIXME: Make this infallible after bug 968520 is done.
+ MOZ_ALWAYS_TRUE(mIndex.AppendElement(sample, fallible));
+ if (indice.start_offset < lastOffset) {
+ NS_WARNING("Chunks in MP4 out of order, expect slow down");
+ progressive = false;
+ }
+ lastOffset = indice.end_offset;
+
+ // Pack audio samples in group of 128.
+ if (sample.mSync && progressive && (!mIsAudio || !(i % 128))) {
+ if (mDataOffset.Length()) {
+ auto& last = mDataOffset.LastElement();
+ last.mEndOffset = intervalRange.mEnd;
+ NS_ASSERTION(intervalTime.Length() == 1, "Discontinuous samples between keyframes");
+ last.mTime.start = intervalTime.GetStart();
+ last.mTime.end = intervalTime.GetEnd();
+ }
+ if (!mDataOffset.AppendElement(MP4DataOffset(mIndex.Length() - 1,
+ indice.start_offset),
+ fallible)) {
+ // OOM.
+ return;
+ }
+ intervalTime = media::IntervalSet<int64_t>();
+ intervalRange = MediaByteRange();
+ }
+ intervalTime += media::Interval<int64_t>(sample.mCompositionRange.start,
+ sample.mCompositionRange.end);
+ intervalRange = intervalRange.Span(sample.mByteRange);
+ }
+
+ if (mDataOffset.Length() && progressive) {
+ auto& last = mDataOffset.LastElement();
+ last.mEndOffset = aIndex.LastElement().end_offset;
+ last.mTime = Interval<int64_t>(intervalTime.GetStart(), intervalTime.GetEnd());
+ } else {
+ mDataOffset.Clear();
+ }
+ }
+}
+
+Index::~Index() {}
+
+void
+Index::UpdateMoofIndex(const MediaByteRangeSet& aByteRanges)
+{
+ UpdateMoofIndex(aByteRanges, false);
+}
+
+void
+Index::UpdateMoofIndex(const MediaByteRangeSet& aByteRanges, bool aCanEvict)
+{
+ if (!mMoofParser) {
+ return;
+ }
+ size_t moofs = mMoofParser->Moofs().Length();
+ bool canEvict = aCanEvict && moofs > 1;
+ if (canEvict) {
+ // Check that we can trim the mMoofParser. We can only do so if all
+ // iterators have demuxed all possible samples.
+ for (const SampleIterator* iterator : mIterators) {
+ if ((iterator->mCurrentSample == 0 && iterator->mCurrentMoof == moofs) ||
+ iterator->mCurrentMoof == moofs - 1) {
+ continue;
+ }
+ canEvict = false;
+ break;
+ }
+ }
+ mMoofParser->RebuildFragmentedIndex(aByteRanges, &canEvict);
+ if (canEvict) {
+ // The moofparser got trimmed. Adjust all registered iterators.
+ for (SampleIterator* iterator : mIterators) {
+ iterator->mCurrentMoof -= moofs - 1;
+ }
+ }
+}
+
+Microseconds
+Index::GetEndCompositionIfBuffered(const MediaByteRangeSet& aByteRanges)
+{
+ FallibleTArray<Sample>* index;
+ if (mMoofParser) {
+ if (!mMoofParser->ReachedEnd() || mMoofParser->Moofs().IsEmpty()) {
+ return 0;
+ }
+ index = &mMoofParser->Moofs().LastElement().mIndex;
+ } else {
+ index = &mIndex;
+ }
+
+ Microseconds lastComposition = 0;
+ RangeFinder rangeFinder(aByteRanges);
+ for (size_t i = index->Length(); i--;) {
+ const Sample& sample = (*index)[i];
+ if (!rangeFinder.Contains(sample.mByteRange)) {
+ return 0;
+ }
+ lastComposition = std::max(lastComposition, sample.mCompositionRange.end);
+ if (sample.mSync) {
+ return lastComposition;
+ }
+ }
+ return 0;
+}
+
+TimeIntervals
+Index::ConvertByteRangesToTimeRanges(const MediaByteRangeSet& aByteRanges)
+{
+ if (aByteRanges == mLastCachedRanges) {
+ return mLastBufferedRanges;
+ }
+ mLastCachedRanges = aByteRanges;
+
+ if (mDataOffset.Length()) {
+ TimeIntervals timeRanges;
+ for (const auto& range : aByteRanges) {
+ uint32_t start = mDataOffset.IndexOfFirstElementGt(range.mStart - 1);
+ if (!mIsAudio && start == mDataOffset.Length()) {
+ continue;
+ }
+ uint32_t end = mDataOffset.IndexOfFirstElementGt(range.mEnd, MP4DataOffset::EndOffsetComparator());
+ if (!mIsAudio && end < start) {
+ continue;
+ }
+ if (mIsAudio && start &&
+ range.Intersects(MediaByteRange(mDataOffset[start-1].mStartOffset,
+ mDataOffset[start-1].mEndOffset))) {
+ // Check if previous audio data block contains some available samples.
+ for (size_t i = mDataOffset[start-1].mIndex; i < mIndex.Length(); i++) {
+ if (range.ContainsStrict(mIndex[i].mByteRange)) {
+ timeRanges +=
+ TimeInterval(TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
+ TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
+ }
+ }
+ }
+ if (end > start) {
+ timeRanges +=
+ TimeInterval(TimeUnit::FromMicroseconds(mDataOffset[start].mTime.start),
+ TimeUnit::FromMicroseconds(mDataOffset[end-1].mTime.end));
+ }
+ if (end < mDataOffset.Length()) {
+ // Find samples in partial block contained in the byte range.
+ for (size_t i = mDataOffset[end].mIndex;
+ i < mIndex.Length() && range.ContainsStrict(mIndex[i].mByteRange);
+ i++) {
+ timeRanges +=
+ TimeInterval(TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
+ TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
+ }
+ }
+ }
+ mLastBufferedRanges = timeRanges;
+ return timeRanges;
+ }
+
+ RangeFinder rangeFinder(aByteRanges);
+ nsTArray<Interval<Microseconds>> timeRanges;
+ nsTArray<FallibleTArray<Sample>*> indexes;
+ if (mMoofParser) {
+ // We take the index out of the moof parser and move it into a local
+ // variable so we don't get concurrency issues. It gets freed when we
+ // exit this function.
+ for (int i = 0; i < mMoofParser->Moofs().Length(); i++) {
+ Moof& moof = mMoofParser->Moofs()[i];
+
+ // We need the entire moof in order to play anything
+ if (rangeFinder.Contains(moof.mRange)) {
+ if (rangeFinder.Contains(moof.mMdatRange)) {
+ Interval<Microseconds>::SemiNormalAppend(timeRanges, moof.mTimeRange);
+ } else {
+ indexes.AppendElement(&moof.mIndex);
+ }
+ }
+ }
+ } else {
+ indexes.AppendElement(&mIndex);
+ }
+
+ bool hasSync = false;
+ for (size_t i = 0; i < indexes.Length(); i++) {
+ FallibleTArray<Sample>* index = indexes[i];
+ for (size_t j = 0; j < index->Length(); j++) {
+ const Sample& sample = (*index)[j];
+ if (!rangeFinder.Contains(sample.mByteRange)) {
+ // We process the index in decode order so we clear hasSync when we hit
+ // a range that isn't buffered.
+ hasSync = false;
+ continue;
+ }
+
+ hasSync |= sample.mSync;
+ if (!hasSync) {
+ continue;
+ }
+
+ Interval<Microseconds>::SemiNormalAppend(timeRanges,
+ sample.mCompositionRange);
+ }
+ }
+
+ // This fixes up when the compositon order differs from the byte range order
+ nsTArray<Interval<Microseconds>> timeRangesNormalized;
+ Interval<Microseconds>::Normalize(timeRanges, &timeRangesNormalized);
+ // convert timeRanges.
+ media::TimeIntervals ranges;
+ for (size_t i = 0; i < timeRangesNormalized.Length(); i++) {
+ ranges +=
+ media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].start),
+ media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].end));
+ }
+ mLastBufferedRanges = ranges;
+ return ranges;
+}
+
+uint64_t
+Index::GetEvictionOffset(Microseconds aTime)
+{
+ uint64_t offset = std::numeric_limits<uint64_t>::max();
+ if (mMoofParser) {
+ // We need to keep the whole moof if we're keeping any of it because the
+ // parser doesn't keep parsed moofs.
+ for (int i = 0; i < mMoofParser->Moofs().Length(); i++) {
+ Moof& moof = mMoofParser->Moofs()[i];
+
+ if (moof.mTimeRange.Length() && moof.mTimeRange.end > aTime) {
+ offset = std::min(offset, uint64_t(std::min(moof.mRange.mStart,
+ moof.mMdatRange.mStart)));
+ }
+ }
+ } else {
+ // We've already parsed and stored the moov so we don't need to keep it.
+ // All we need to keep is the sample data itself.
+ for (size_t i = 0; i < mIndex.Length(); i++) {
+ const Sample& sample = mIndex[i];
+ if (aTime >= sample.mCompositionRange.end) {
+ offset = std::min(offset, uint64_t(sample.mByteRange.mEnd));
+ }
+ }
+ }
+ return offset;
+}
+
+void
+Index::RegisterIterator(SampleIterator* aIterator)
+{
+ mIterators.AppendElement(aIterator);
+}
+
+void
+Index::UnregisterIterator(SampleIterator* aIterator)
+{
+ mIterators.RemoveElement(aIterator);
+}
+
+}
diff --git a/media/libstagefright/binding/MP4Metadata.cpp b/media/libstagefright/binding/MP4Metadata.cpp
new file mode 100644
index 000000000..eb5350704
--- /dev/null
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -0,0 +1,880 @@
+/* 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 "include/MPEG4Extractor.h"
+#include "media/stagefright/DataSource.h"
+#include "media/stagefright/MediaDefs.h"
+#include "media/stagefright/MediaSource.h"
+#include "media/stagefright/MetaData.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "VideoUtils.h"
+#include "mp4_demuxer/MoofParser.h"
+#include "mp4_demuxer/MP4Metadata.h"
+#include "mp4_demuxer/Stream.h"
+#include "MediaPrefs.h"
+
+#include <limits>
+#include <stdint.h>
+#include <vector>
+
+#ifdef MOZ_RUST_MP4PARSE
+// OpusDecoder header is really needed only by MP4 in rust
+#include "OpusDecoder.h"
+#include "mp4parse.h"
+
+struct FreeMP4Parser { void operator()(mp4parse_parser* aPtr) { mp4parse_free(aPtr); } };
+#endif
+
+using namespace stagefright;
+
+namespace mp4_demuxer
+{
+
+class DataSourceAdapter : public DataSource
+{
+public:
+ explicit DataSourceAdapter(Stream* aSource) : mSource(aSource) {}
+
+ ~DataSourceAdapter() {}
+
+ virtual status_t initCheck() const { return NO_ERROR; }
+
+ virtual ssize_t readAt(off64_t offset, void* data, size_t size)
+ {
+ MOZ_ASSERT(((ssize_t)size) >= 0);
+ size_t bytesRead;
+ if (!mSource->ReadAt(offset, data, size, &bytesRead))
+ return ERROR_IO;
+
+ if (bytesRead == 0)
+ return ERROR_END_OF_STREAM;
+
+ MOZ_ASSERT(((ssize_t)bytesRead) > 0);
+ return bytesRead;
+ }
+
+ virtual status_t getSize(off64_t* size)
+ {
+ if (!mSource->Length(size))
+ return ERROR_UNSUPPORTED;
+ return NO_ERROR;
+ }
+
+ virtual uint32_t flags() { return kWantsPrefetching | kIsHTTPBasedSource; }
+
+ virtual status_t reconnectAtOffset(off64_t offset) { return NO_ERROR; }
+
+private:
+ RefPtr<Stream> mSource;
+};
+
+class MP4MetadataStagefright
+{
+public:
+ explicit MP4MetadataStagefright(Stream* aSource);
+ ~MP4MetadataStagefright();
+
+ static bool HasCompleteMetadata(Stream* aSource);
+ static already_AddRefed<mozilla::MediaByteBuffer> Metadata(Stream* aSource);
+ uint32_t GetNumberTracks(mozilla::TrackInfo::TrackType aType) const;
+ mozilla::UniquePtr<mozilla::TrackInfo> GetTrackInfo(mozilla::TrackInfo::TrackType aType,
+ size_t aTrackNumber) const;
+ bool CanSeek() const;
+
+ const CryptoFile& Crypto() const;
+
+ bool ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID);
+
+private:
+ int32_t GetTrackNumber(mozilla::TrackID aTrackID);
+ void UpdateCrypto(const stagefright::MetaData* aMetaData);
+ mozilla::UniquePtr<mozilla::TrackInfo> CheckTrack(const char* aMimeType,
+ stagefright::MetaData* aMetaData,
+ int32_t aIndex) const;
+ CryptoFile mCrypto;
+ RefPtr<Stream> mSource;
+ sp<MediaExtractor> mMetadataExtractor;
+ bool mCanSeek;
+};
+
+#ifdef MOZ_RUST_MP4PARSE
+
+// Wrap an mp4_demuxer::Stream to remember the read offset.
+
+class RustStreamAdaptor {
+public:
+ explicit RustStreamAdaptor(Stream* aSource)
+ : mSource(aSource)
+ , mOffset(0)
+ {
+ }
+
+ ~RustStreamAdaptor() {}
+
+ bool Read(uint8_t* buffer, uintptr_t size, size_t* bytes_read);
+
+private:
+ Stream* mSource;
+ CheckedInt<size_t> mOffset;
+};
+
+class MP4MetadataRust
+{
+public:
+ explicit MP4MetadataRust(Stream* aSource);
+ ~MP4MetadataRust();
+
+ static bool HasCompleteMetadata(Stream* aSource);
+ static already_AddRefed<mozilla::MediaByteBuffer> Metadata(Stream* aSource);
+ uint32_t GetNumberTracks(mozilla::TrackInfo::TrackType aType) const;
+ mozilla::UniquePtr<mozilla::TrackInfo> GetTrackInfo(mozilla::TrackInfo::TrackType aType,
+ size_t aTrackNumber) const;
+ bool CanSeek() const;
+
+ const CryptoFile& Crypto() const;
+
+ bool ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID);
+
+private:
+ Maybe<uint32_t> TrackTypeToGlobalTrackIndex(mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const;
+
+ CryptoFile mCrypto;
+ RefPtr<Stream> mSource;
+ RustStreamAdaptor mRustSource;
+ mozilla::UniquePtr<mp4parse_parser, FreeMP4Parser> mRustParser;
+};
+#endif
+
+MP4Metadata::MP4Metadata(Stream* aSource)
+ : mStagefright(MakeUnique<MP4MetadataStagefright>(aSource))
+#ifdef MOZ_RUST_MP4PARSE
+ , mRust(MakeUnique<MP4MetadataRust>(aSource))
+ , mPreferRust(false)
+ , mReportedAudioTrackTelemetry(false)
+ , mReportedVideoTrackTelemetry(false)
+#ifndef RELEASE_OR_BETA
+ , mRustTestMode(MediaPrefs::RustTestMode())
+#endif
+#endif
+{
+}
+
+MP4Metadata::~MP4Metadata()
+{
+}
+
+/*static*/ bool
+MP4Metadata::HasCompleteMetadata(Stream* aSource)
+{
+ return MP4MetadataStagefright::HasCompleteMetadata(aSource);
+}
+
+/*static*/ already_AddRefed<mozilla::MediaByteBuffer>
+MP4Metadata::Metadata(Stream* aSource)
+{
+ return MP4MetadataStagefright::Metadata(aSource);
+}
+
+static const char *
+TrackTypeToString(mozilla::TrackInfo::TrackType aType)
+{
+ switch (aType) {
+ case mozilla::TrackInfo::kAudioTrack:
+ return "audio";
+ case mozilla::TrackInfo::kVideoTrack:
+ return "video";
+ default:
+ return "unknown";
+ }
+}
+
+uint32_t
+MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
+{
+ static LazyLogModule sLog("MP4Metadata");
+
+ uint32_t numTracks = mStagefright->GetNumberTracks(aType);
+
+#ifdef MOZ_RUST_MP4PARSE
+ if (!mRust) {
+ return numTracks;
+ }
+
+ uint32_t numTracksRust = mRust->GetNumberTracks(aType);
+ MOZ_LOG(sLog, LogLevel::Info, ("%s tracks found: stagefright=%u rust=%u",
+ TrackTypeToString(aType), numTracks, numTracksRust));
+
+ bool numTracksMatch = numTracks == numTracksRust;
+
+ if (aType == mozilla::TrackInfo::kAudioTrack && !mReportedAudioTrackTelemetry) {
+ Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_TRACK_MATCH_AUDIO,
+ numTracksMatch);
+ mReportedAudioTrackTelemetry = true;
+ } else if (aType == mozilla::TrackInfo::kVideoTrack && !mReportedVideoTrackTelemetry) {
+ Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_TRACK_MATCH_VIDEO,
+ numTracksMatch);
+ mReportedVideoTrackTelemetry = true;
+ }
+
+ if (mPreferRust || ShouldPreferRust()) {
+ MOZ_LOG(sLog, LogLevel::Info, ("Preferring rust demuxer"));
+ mPreferRust = true;
+ return numTracksRust;
+ }
+#endif // MOZ_RUST_MP4PARSE
+
+ return numTracks;
+}
+
+#ifdef MOZ_RUST_MP4PARSE
+bool MP4Metadata::ShouldPreferRust() const {
+ if (!mRust) {
+ return false;
+ }
+ // See if there's an Opus track.
+ uint32_t numTracks = mRust->GetNumberTracks(TrackInfo::kAudioTrack);
+ for (auto i = 0; i < numTracks; i++) {
+ auto info = mRust->GetTrackInfo(TrackInfo::kAudioTrack, i);
+ if (!info) {
+ return false;
+ }
+ if (info->mMimeType.EqualsASCII("audio/opus") ||
+ info->mMimeType.EqualsASCII("audio/flac")) {
+ return true;
+ }
+ }
+
+ numTracks = mRust->GetNumberTracks(TrackInfo::kVideoTrack);
+ for (auto i = 0; i < numTracks; i++) {
+ auto info = mRust->GetTrackInfo(TrackInfo::kVideoTrack, i);
+ if (!info) {
+ return false;
+ }
+ if (info->mMimeType.EqualsASCII("video/vp9")) {
+ return true;
+ }
+ }
+ // Otherwise, fall back.
+ return false;
+}
+#endif // MOZ_RUST_MP4PARSE
+
+mozilla::UniquePtr<mozilla::TrackInfo>
+MP4Metadata::GetTrackInfo(mozilla::TrackInfo::TrackType aType,
+ size_t aTrackNumber) const
+{
+ mozilla::UniquePtr<mozilla::TrackInfo> info =
+ mStagefright->GetTrackInfo(aType, aTrackNumber);
+
+#ifdef MOZ_RUST_MP4PARSE
+ if (!mRust) {
+ return info;
+ }
+
+ mozilla::UniquePtr<mozilla::TrackInfo> infoRust =
+ mRust->GetTrackInfo(aType, aTrackNumber);
+
+#ifndef RELEASE_OR_BETA
+ if (mRustTestMode && info) {
+ MOZ_DIAGNOSTIC_ASSERT(infoRust);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mId == info->mId);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mKind == info->mKind);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mLabel == info->mLabel);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mLanguage == info->mLanguage);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mEnabled == info->mEnabled);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mTrackId == info->mTrackId);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mMimeType == info->mMimeType);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mDuration == info->mDuration);
+ MOZ_DIAGNOSTIC_ASSERT(infoRust->mMediaTime == info->mMediaTime);
+ switch (aType) {
+ case mozilla::TrackInfo::kAudioTrack: {
+ AudioInfo *audioRust = infoRust->GetAsAudioInfo(), *audio = info->GetAsAudioInfo();
+ MOZ_DIAGNOSTIC_ASSERT(audioRust->mRate == audio->mRate);
+ MOZ_DIAGNOSTIC_ASSERT(audioRust->mChannels == audio->mChannels);
+ MOZ_DIAGNOSTIC_ASSERT(audioRust->mBitDepth == audio->mBitDepth);
+ // TODO: These fields aren't implemented in the Rust demuxer yet.
+ //MOZ_DIAGNOSTIC_ASSERT(audioRust->mProfile != audio->mProfile);
+ //MOZ_DIAGNOSTIC_ASSERT(audioRust->mExtendedProfile != audio->mExtendedProfile);
+ break;
+ }
+ case mozilla::TrackInfo::kVideoTrack: {
+ VideoInfo *videoRust = infoRust->GetAsVideoInfo(), *video = info->GetAsVideoInfo();
+ MOZ_DIAGNOSTIC_ASSERT(videoRust->mDisplay == video->mDisplay);
+ MOZ_DIAGNOSTIC_ASSERT(videoRust->mImage == video->mImage);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+#endif
+
+ if (!mPreferRust) {
+ return info;
+ }
+ MOZ_ASSERT(infoRust);
+ return infoRust;
+#endif
+
+ return info;
+}
+
+bool
+MP4Metadata::CanSeek() const
+{
+ return mStagefright->CanSeek();
+}
+
+const CryptoFile&
+MP4Metadata::Crypto() const
+{
+ return mStagefright->Crypto();
+}
+
+bool
+MP4Metadata::ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID)
+{
+#ifdef MOZ_RUST_MP4PARSE
+ if (mRust && mPreferRust && mRust->ReadTrackIndex(aDest, aTrackID)) {
+ return true;
+ }
+ aDest.Clear();
+#endif
+ return mStagefright->ReadTrackIndex(aDest, aTrackID);
+}
+
+static inline bool
+ConvertIndex(FallibleTArray<Index::Indice>& aDest,
+ const nsTArray<stagefright::MediaSource::Indice>& aIndex,
+ int64_t aMediaTime)
+{
+ if (!aDest.SetCapacity(aIndex.Length(), mozilla::fallible)) {
+ return false;
+ }
+ for (size_t i = 0; i < aIndex.Length(); i++) {
+ Index::Indice indice;
+ const stagefright::MediaSource::Indice& s_indice = aIndex[i];
+ indice.start_offset = s_indice.start_offset;
+ indice.end_offset = s_indice.end_offset;
+ indice.start_composition = s_indice.start_composition - aMediaTime;
+ indice.end_composition = s_indice.end_composition - aMediaTime;
+ indice.start_decode = s_indice.start_decode;
+ indice.sync = s_indice.sync;
+ // FIXME: Make this infallible after bug 968520 is done.
+ MOZ_ALWAYS_TRUE(aDest.AppendElement(indice, mozilla::fallible));
+ }
+ return true;
+}
+
+MP4MetadataStagefright::MP4MetadataStagefright(Stream* aSource)
+ : mSource(aSource)
+ , mMetadataExtractor(new MPEG4Extractor(new DataSourceAdapter(mSource)))
+ , mCanSeek(mMetadataExtractor->flags() & MediaExtractor::CAN_SEEK)
+{
+ sp<MetaData> metaData = mMetadataExtractor->getMetaData();
+
+ if (metaData.get()) {
+ UpdateCrypto(metaData.get());
+ }
+}
+
+MP4MetadataStagefright::~MP4MetadataStagefright()
+{
+}
+
+uint32_t
+MP4MetadataStagefright::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
+{
+ size_t tracks = mMetadataExtractor->countTracks();
+ uint32_t total = 0;
+ for (size_t i = 0; i < tracks; i++) {
+ sp<MetaData> metaData = mMetadataExtractor->getTrackMetaData(i);
+
+ const char* mimeType;
+ if (metaData == nullptr || !metaData->findCString(kKeyMIMEType, &mimeType)) {
+ continue;
+ }
+ switch (aType) {
+ case mozilla::TrackInfo::kAudioTrack:
+ if (!strncmp(mimeType, "audio/", 6) &&
+ CheckTrack(mimeType, metaData.get(), i)) {
+ total++;
+ }
+ break;
+ case mozilla::TrackInfo::kVideoTrack:
+ if (!strncmp(mimeType, "video/", 6) &&
+ CheckTrack(mimeType, metaData.get(), i)) {
+ total++;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return total;
+}
+
+mozilla::UniquePtr<mozilla::TrackInfo>
+MP4MetadataStagefright::GetTrackInfo(mozilla::TrackInfo::TrackType aType,
+ size_t aTrackNumber) const
+{
+ size_t tracks = mMetadataExtractor->countTracks();
+ if (!tracks) {
+ return nullptr;
+ }
+ int32_t index = -1;
+ const char* mimeType;
+ sp<MetaData> metaData;
+
+ size_t i = 0;
+ while (i < tracks) {
+ metaData = mMetadataExtractor->getTrackMetaData(i);
+
+ if (metaData == nullptr || !metaData->findCString(kKeyMIMEType, &mimeType)) {
+ continue;
+ }
+ switch (aType) {
+ case mozilla::TrackInfo::kAudioTrack:
+ if (!strncmp(mimeType, "audio/", 6) &&
+ CheckTrack(mimeType, metaData.get(), i)) {
+ index++;
+ }
+ break;
+ case mozilla::TrackInfo::kVideoTrack:
+ if (!strncmp(mimeType, "video/", 6) &&
+ CheckTrack(mimeType, metaData.get(), i)) {
+ index++;
+ }
+ break;
+ default:
+ break;
+ }
+ if (index == aTrackNumber) {
+ break;
+ }
+ i++;
+ }
+ if (index < 0) {
+ return nullptr;
+ }
+
+ UniquePtr<mozilla::TrackInfo> e = CheckTrack(mimeType, metaData.get(), index);
+
+ if (e) {
+ metaData = mMetadataExtractor->getMetaData();
+ int64_t movieDuration;
+ if (!e->mDuration &&
+ metaData->findInt64(kKeyMovieDuration, &movieDuration)) {
+ // No duration in track, use movie extend header box one.
+ e->mDuration = movieDuration;
+ }
+ }
+
+ return e;
+}
+
+mozilla::UniquePtr<mozilla::TrackInfo>
+MP4MetadataStagefright::CheckTrack(const char* aMimeType,
+ stagefright::MetaData* aMetaData,
+ int32_t aIndex) const
+{
+ sp<MediaSource> track = mMetadataExtractor->getTrack(aIndex);
+ if (!track.get()) {
+ return nullptr;
+ }
+
+ UniquePtr<mozilla::TrackInfo> e;
+
+ if (!strncmp(aMimeType, "audio/", 6)) {
+ auto info = mozilla::MakeUnique<MP4AudioInfo>();
+ info->Update(aMetaData, aMimeType);
+ e = Move(info);
+ } else if (!strncmp(aMimeType, "video/", 6)) {
+ auto info = mozilla::MakeUnique<MP4VideoInfo>();
+ info->Update(aMetaData, aMimeType);
+ e = Move(info);
+ }
+
+ if (e && e->IsValid()) {
+ return e;
+ }
+
+ return nullptr;
+}
+
+bool
+MP4MetadataStagefright::CanSeek() const
+{
+ return mCanSeek;
+}
+
+const CryptoFile&
+MP4MetadataStagefright::Crypto() const
+{
+ return mCrypto;
+}
+
+void
+MP4MetadataStagefright::UpdateCrypto(const MetaData* aMetaData)
+{
+ const void* data;
+ size_t size;
+ uint32_t type;
+
+ // There's no point in checking that the type matches anything because it
+ // isn't set consistently in the MPEG4Extractor.
+ if (!aMetaData->findData(kKeyPssh, &type, &data, &size)) {
+ return;
+ }
+ mCrypto.Update(reinterpret_cast<const uint8_t*>(data), size);
+}
+
+bool
+MP4MetadataStagefright::ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID)
+{
+ size_t numTracks = mMetadataExtractor->countTracks();
+ int32_t trackNumber = GetTrackNumber(aTrackID);
+ if (trackNumber < 0) {
+ return false;
+ }
+ sp<MediaSource> track = mMetadataExtractor->getTrack(trackNumber);
+ if (!track.get()) {
+ return false;
+ }
+ sp<MetaData> metadata = mMetadataExtractor->getTrackMetaData(trackNumber);
+ int64_t mediaTime;
+ if (!metadata->findInt64(kKeyMediaTime, &mediaTime)) {
+ mediaTime = 0;
+ }
+ bool rv = ConvertIndex(aDest, track->exportIndex(), mediaTime);
+
+ return rv;
+}
+
+int32_t
+MP4MetadataStagefright::GetTrackNumber(mozilla::TrackID aTrackID)
+{
+ size_t numTracks = mMetadataExtractor->countTracks();
+ for (size_t i = 0; i < numTracks; i++) {
+ sp<MetaData> metaData = mMetadataExtractor->getTrackMetaData(i);
+ if (!metaData.get()) {
+ continue;
+ }
+ int32_t value;
+ if (metaData->findInt32(kKeyTrackID, &value) && value == aTrackID) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*static*/ bool
+MP4MetadataStagefright::HasCompleteMetadata(Stream* aSource)
+{
+ auto parser = mozilla::MakeUnique<MoofParser>(aSource, 0, false);
+ return parser->HasMetadata();
+}
+
+/*static*/ already_AddRefed<mozilla::MediaByteBuffer>
+MP4MetadataStagefright::Metadata(Stream* aSource)
+{
+ auto parser = mozilla::MakeUnique<MoofParser>(aSource, 0, false);
+ return parser->Metadata();
+}
+
+#ifdef MOZ_RUST_MP4PARSE
+bool
+RustStreamAdaptor::Read(uint8_t* buffer, uintptr_t size, size_t* bytes_read)
+{
+ if (!mOffset.isValid()) {
+ static LazyLogModule sLog("MP4Metadata");
+ MOZ_LOG(sLog, LogLevel::Error, ("Overflow in source stream offset"));
+ return false;
+ }
+ bool rv = mSource->ReadAt(mOffset.value(), buffer, size, bytes_read);
+ if (rv) {
+ mOffset += *bytes_read;
+ }
+ return rv;
+}
+
+// Wrapper to allow rust to call our read adaptor.
+static intptr_t
+read_source(uint8_t* buffer, uintptr_t size, void* userdata)
+{
+ MOZ_ASSERT(buffer);
+ MOZ_ASSERT(userdata);
+
+ auto source = reinterpret_cast<RustStreamAdaptor*>(userdata);
+ size_t bytes_read = 0;
+ bool rv = source->Read(buffer, size, &bytes_read);
+ if (!rv) {
+ static LazyLogModule sLog("MP4Metadata");
+ MOZ_LOG(sLog, LogLevel::Warning, ("Error reading source data"));
+ return -1;
+ }
+ return bytes_read;
+}
+
+MP4MetadataRust::MP4MetadataRust(Stream* aSource)
+ : mSource(aSource)
+ , mRustSource(aSource)
+{
+ mp4parse_io io = { read_source, &mRustSource };
+ mRustParser.reset(mp4parse_new(&io));
+ MOZ_ASSERT(mRustParser);
+
+ static LazyLogModule sLog("MP4Metadata");
+ mp4parse_error rv = mp4parse_read(mRustParser.get());
+ MOZ_LOG(sLog, LogLevel::Debug, ("rust parser returned %d\n", rv));
+ Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS,
+ rv == MP4PARSE_OK);
+ if (rv != MP4PARSE_OK) {
+ MOZ_ASSERT(rv > 0);
+ Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_ERROR_CODE, rv);
+ }
+}
+
+MP4MetadataRust::~MP4MetadataRust()
+{
+}
+
+bool
+TrackTypeEqual(TrackInfo::TrackType aLHS, mp4parse_track_type aRHS)
+{
+ switch (aLHS) {
+ case TrackInfo::kAudioTrack:
+ return aRHS == MP4PARSE_TRACK_TYPE_AUDIO;
+ case TrackInfo::kVideoTrack:
+ return aRHS == MP4PARSE_TRACK_TYPE_VIDEO;
+ default:
+ return false;
+ }
+}
+
+uint32_t
+MP4MetadataRust::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
+{
+ static LazyLogModule sLog("MP4Metadata");
+
+ uint32_t tracks;
+ auto rv = mp4parse_get_track_count(mRustParser.get(), &tracks);
+ if (rv != MP4PARSE_OK) {
+ MOZ_LOG(sLog, LogLevel::Warning,
+ ("rust parser error %d counting tracks", rv));
+ return 0;
+ }
+ MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %u tracks", tracks));
+
+ uint32_t total = 0;
+ for (uint32_t i = 0; i < tracks; ++i) {
+ mp4parse_track_info track_info;
+ rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
+ if (rv != MP4PARSE_OK) {
+ continue;
+ }
+ if (TrackTypeEqual(aType, track_info.track_type)) {
+ total += 1;
+ }
+ }
+
+ return total;
+}
+
+Maybe<uint32_t>
+MP4MetadataRust::TrackTypeToGlobalTrackIndex(mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const
+{
+ uint32_t tracks;
+ auto rv = mp4parse_get_track_count(mRustParser.get(), &tracks);
+ if (rv != MP4PARSE_OK) {
+ return Nothing();
+ }
+
+ /* The MP4Metadata API uses a per-TrackType index of tracks, but mp4parse
+ (and libstagefright) use a global track index. Convert the index by
+ counting the tracks of the requested type and returning the global
+ track index when a match is found. */
+ uint32_t perType = 0;
+ for (uint32_t i = 0; i < tracks; ++i) {
+ mp4parse_track_info track_info;
+ rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
+ if (rv != MP4PARSE_OK) {
+ continue;
+ }
+ if (TrackTypeEqual(aType, track_info.track_type)) {
+ if (perType == aTrackNumber) {
+ return Some(i);
+ }
+ perType += 1;
+ }
+ }
+
+ return Nothing();
+}
+
+mozilla::UniquePtr<mozilla::TrackInfo>
+MP4MetadataRust::GetTrackInfo(mozilla::TrackInfo::TrackType aType,
+ size_t aTrackNumber) const
+{
+ static LazyLogModule sLog("MP4Metadata");
+
+ Maybe<uint32_t> trackIndex = TrackTypeToGlobalTrackIndex(aType, aTrackNumber);
+ if (trackIndex.isNothing()) {
+ return nullptr;
+ }
+
+ mp4parse_track_info info;
+ auto rv = mp4parse_get_track_info(mRustParser.get(), trackIndex.value(), &info);
+ if (rv != MP4PARSE_OK) {
+ MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_info returned %d", rv));
+ return nullptr;
+ }
+#ifdef DEBUG
+ const char* codec_string = "unrecognized";
+ switch (info.codec) {
+ case MP4PARSE_CODEC_UNKNOWN: codec_string = "unknown"; break;
+ case MP4PARSE_CODEC_AAC: codec_string = "aac"; break;
+ case MP4PARSE_CODEC_OPUS: codec_string = "opus"; break;
+ case MP4PARSE_CODEC_FLAC: codec_string = "flac"; break;
+ case MP4PARSE_CODEC_AVC: codec_string = "h.264"; break;
+ case MP4PARSE_CODEC_VP9: codec_string = "vp9"; break;
+ case MP4PARSE_CODEC_MP3: codec_string = "mp3"; break;
+ }
+ MOZ_LOG(sLog, LogLevel::Debug, ("track codec %s (%u)\n",
+ codec_string, info.codec));
+#endif
+
+ // This specialization interface is crazy.
+ UniquePtr<mozilla::TrackInfo> e;
+ switch (aType) {
+ case TrackInfo::TrackType::kAudioTrack: {
+ mp4parse_track_audio_info audio;
+ auto rv = mp4parse_get_track_audio_info(mRustParser.get(), trackIndex.value(), &audio);
+ if (rv != MP4PARSE_OK) {
+ MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_audio_info returned error %d", rv));
+ return nullptr;
+ }
+ auto track = mozilla::MakeUnique<mozilla::AudioInfo>();
+ if (info.codec == MP4PARSE_CODEC_OPUS) {
+ track->mMimeType = NS_LITERAL_CSTRING("audio/opus");
+ // The Opus decoder expects the container's codec delay or
+ // pre-skip value, in microseconds, as a 64-bit int at the
+ // start of the codec-specific config blob.
+ MOZ_ASSERT(audio.codec_specific_config.data);
+ MOZ_ASSERT(audio.codec_specific_config.length >= 12);
+ uint16_t preskip =
+ LittleEndian::readUint16(audio.codec_specific_config.data + 10);
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("Copying opus pre-skip value of %d as CodecDelay.",(int)preskip));
+ OpusDataDecoder::AppendCodecDelay(track->mCodecSpecificConfig,
+ mozilla::FramesToUsecs(preskip, 48000).value());
+ } else if (info.codec == MP4PARSE_CODEC_AAC) {
+ track->mMimeType = MEDIA_MIMETYPE_AUDIO_AAC;
+ } else if (info.codec == MP4PARSE_CODEC_FLAC) {
+ track->mMimeType = MEDIA_MIMETYPE_AUDIO_FLAC;
+ } else if (info.codec == MP4PARSE_CODEC_MP3) {
+ track->mMimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
+ }
+ track->mCodecSpecificConfig->AppendElements(
+ audio.codec_specific_config.data,
+ audio.codec_specific_config.length);
+ track->mRate = audio.sample_rate;
+ track->mChannels = audio.channels;
+ track->mBitDepth = audio.bit_depth;
+ track->mDuration = info.duration;
+ track->mMediaTime = info.media_time;
+ track->mTrackId = info.track_id;
+ e = Move(track);
+ }
+ break;
+ case TrackInfo::TrackType::kVideoTrack: {
+ mp4parse_track_video_info video;
+ auto rv = mp4parse_get_track_video_info(mRustParser.get(), trackIndex.value(), &video);
+ if (rv != MP4PARSE_OK) {
+ MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_video_info returned error %d", rv));
+ return nullptr;
+ }
+ auto track = mozilla::MakeUnique<MP4VideoInfo>();
+ track->Update(&info, &video);
+ e = Move(track);
+ }
+ break;
+ default:
+ MOZ_LOG(sLog, LogLevel::Warning, ("unhandled track type %d", aType));
+ return nullptr;
+ break;
+ }
+
+ // No duration in track, use fragment_duration.
+ if (e && !e->mDuration) {
+ mp4parse_fragment_info info;
+ auto rv = mp4parse_get_fragment_info(mRustParser.get(), &info);
+ if (rv == MP4PARSE_OK) {
+ e->mDuration = info.fragment_duration;
+ }
+ }
+
+ if (e && e->IsValid()) {
+ return e;
+ }
+ MOZ_LOG(sLog, LogLevel::Debug, ("TrackInfo didn't validate"));
+
+ return nullptr;
+}
+
+bool
+MP4MetadataRust::CanSeek() const
+{
+ MOZ_ASSERT(false, "Not yet implemented");
+ return false;
+}
+
+const CryptoFile&
+MP4MetadataRust::Crypto() const
+{
+ MOZ_ASSERT(false, "Not yet implemented");
+ return mCrypto;
+}
+
+bool
+MP4MetadataRust::ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID)
+{
+ uint8_t fragmented = false;
+ auto rv = mp4parse_is_fragmented(mRustParser.get(), aTrackID, &fragmented);
+ if (rv != MP4PARSE_OK) {
+ return false;
+ }
+
+ if (fragmented) {
+ return true;
+ }
+
+ // For non-fragmented mp4.
+ NS_WARNING("Not yet implemented");
+
+ return false;
+}
+
+/*static*/ bool
+MP4MetadataRust::HasCompleteMetadata(Stream* aSource)
+{
+ MOZ_ASSERT(false, "Not yet implemented");
+ return false;
+}
+
+/*static*/ already_AddRefed<mozilla::MediaByteBuffer>
+MP4MetadataRust::Metadata(Stream* aSource)
+{
+ MOZ_ASSERT(false, "Not yet implemented");
+ return nullptr;
+}
+#endif
+
+} // namespace mp4_demuxer
diff --git a/media/libstagefright/binding/MoofParser.cpp b/media/libstagefright/binding/MoofParser.cpp
new file mode 100644
index 000000000..ced054282
--- /dev/null
+++ b/media/libstagefright/binding/MoofParser.cpp
@@ -0,0 +1,925 @@
+/* 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 "mp4_demuxer/MoofParser.h"
+#include "mp4_demuxer/Box.h"
+#include "mp4_demuxer/SinfParser.h"
+#include <limits>
+#include "Intervals.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Logging.h"
+
+#if defined(MOZ_FMP4)
+extern mozilla::LogModule* GetDemuxerLog();
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define LOG(name, arg, ...) MOZ_LOG(GetDemuxerLog(), mozilla::LogLevel::Debug, (TOSTRING(name) "(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#else
+#define LOG(...)
+#endif
+
+namespace mp4_demuxer
+{
+
+using namespace stagefright;
+using namespace mozilla;
+
+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)
+{
+ bool foundValidMoof = false;
+ bool foundMdat = false;
+
+ for (Box box(&aContext, mOffset); box.IsAvailable(); 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, mTrex, mMvhd, mMdhd, mEdts, mSinf, &mLastDecodeTime, mIsAudio);
+
+ if (!moof.IsValid() && !box.Next().IsAvailable()) {
+ // Moof isn't valid abort search for now.
+ break;
+ }
+
+ if (!mMoofs.IsEmpty()) {
+ // Stitch time ranges together in the case of a (hopefully small) time
+ // range gap between moofs.
+ mMoofs.LastElement().FixRounding(moof);
+ }
+
+ mMoofs.AppendElement(moof);
+ mMediaRanges.AppendElement(moof.mRange);
+ 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());
+ }
+ }
+ mOffset = box.NextOffset();
+ }
+ 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();
+}
+
+class BlockingStream : public Stream {
+public:
+ explicit BlockingStream(Stream* aStream) : mStream(aStream)
+ {
+ }
+
+ bool ReadAt(int64_t offset, void* data, size_t size, size_t* bytes_read)
+ override
+ {
+ return mStream->ReadAt(offset, data, size, bytes_read);
+ }
+
+ bool 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<Stream> mStream;
+};
+
+bool
+MoofParser::BlockingReadNextMoof()
+{
+ int64_t length = std::numeric_limits<int64_t>::max();
+ mSource->Length(&length);
+ MediaByteRangeSet byteRanges;
+ byteRanges += MediaByteRange(0, length);
+ RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource);
+
+ BoxContext context(stream, byteRanges);
+ for (Box box(&context, mOffset); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("moof")) {
+ byteRanges.Clear();
+ byteRanges += MediaByteRange(mOffset, box.Range().mEnd);
+ return RebuildFragmentedIndex(context);
+ }
+ }
+ return false;
+}
+
+void
+MoofParser::ScanForMetadata(mozilla::MediaByteRange& aFtyp,
+ mozilla::MediaByteRange& aMoov)
+{
+ int64_t length = std::numeric_limits<int64_t>::max();
+ mSource->Length(&length);
+ MediaByteRangeSet byteRanges;
+ byteRanges += MediaByteRange(0, length);
+ RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource);
+
+ BoxContext context(stream, byteRanges);
+ for (Box box(&context, mOffset); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("ftyp")) {
+ aFtyp = box.Range();
+ continue;
+ }
+ if (box.IsType("moov")) {
+ aMoov = box.Range();
+ break;
+ }
+ }
+ mInitRange = aFtyp.Span(aMoov);
+}
+
+bool
+MoofParser::HasMetadata()
+{
+ MediaByteRange ftyp;
+ MediaByteRange moov;
+ ScanForMetadata(ftyp, moov);
+ return !!ftyp.Length() && !!moov.Length();
+}
+
+already_AddRefed<mozilla::MediaByteBuffer>
+MoofParser::Metadata()
+{
+ MediaByteRange ftyp;
+ MediaByteRange moov;
+ ScanForMetadata(ftyp, moov);
+ CheckedInt<MediaByteBuffer::size_type> ftypLength = ftyp.Length();
+ CheckedInt<MediaByteBuffer::size_type> moovLength = moov.Length();
+ if (!ftypLength.isValid() || !moovLength.isValid()
+ || !ftypLength.value() || !moovLength.value()) {
+ // No ftyp or moov, or they cannot be used as array size.
+ return nullptr;
+ }
+ CheckedInt<MediaByteBuffer::size_type> totalLength = ftypLength + moovLength;
+ if (!totalLength.isValid()) {
+ // Addition overflow, or sum cannot be used as array size.
+ return nullptr;
+ }
+ RefPtr<MediaByteBuffer> metadata = new MediaByteBuffer();
+ if (!metadata->SetLength(totalLength.value(), fallible)) {
+ // OOM
+ return nullptr;
+ }
+
+ RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource);
+ size_t read;
+ bool rv =
+ stream->ReadAt(ftyp.mStart, metadata->Elements(), ftypLength.value(), &read);
+ if (!rv || read != ftypLength.value()) {
+ return nullptr;
+ }
+ rv =
+ stream->ReadAt(moov.mStart, metadata->Elements() + ftypLength.value(), moovLength.value(), &read);
+ if (!rv || read != moovLength.value()) {
+ return nullptr;
+ }
+ return metadata.forget();
+}
+
+Interval<Microseconds>
+MoofParser::GetCompositionRange(const MediaByteRangeSet& aByteRanges)
+{
+ Interval<Microseconds> 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);
+ }
+ }
+ return compositionRange;
+}
+
+bool
+MoofParser::ReachedEnd()
+{
+ int64_t length;
+ return mSource->Length(&length) && mOffset == length;
+}
+
+void
+MoofParser::ParseMoov(Box& aBox)
+{
+ 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);
+ }
+ }
+}
+
+void
+MoofParser::ParseTrak(Box& aBox)
+{
+ 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 (!mTrex.mTrackId || tkhd.mTrackId == mTrex.mTrackId) {
+ ParseMdia(box, tkhd);
+ }
+ } else if (box.IsType("edts") &&
+ (!mTrex.mTrackId || tkhd.mTrackId == mTrex.mTrackId)) {
+ mEdts = Edts(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseMdia(Box& aBox, Tkhd& aTkhd)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("mdhd")) {
+ mMdhd = Mdhd(box);
+ } else if (box.IsType("minf")) {
+ ParseMinf(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseMvex(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("trex")) {
+ Trex trex = Trex(box);
+ if (!mTrex.mTrackId || trex.mTrackId == mTrex.mTrackId) {
+ auto trackId = mTrex.mTrackId;
+ mTrex = trex;
+ // Keep the original trackId, as should it be 0 we want to continue
+ // parsing all tracks.
+ mTrex.mTrackId = trackId;
+ }
+ }
+ }
+}
+
+void
+MoofParser::ParseMinf(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("stbl")) {
+ ParseStbl(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseStbl(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("stsd")) {
+ ParseStsd(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseStsd(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("encv") || box.IsType("enca")) {
+ ParseEncrypted(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseEncrypted(Box& aBox)
+{
+ 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;
+ }
+ }
+ }
+}
+
+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(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio)
+ : mRange(aBox.Range())
+ , mMaxRoundingError(35000)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("traf")) {
+ ParseTraf(box, aTrex, aMvhd, aMdhd, aEdts, aSinf, aDecodeTime, aIsAudio);
+ }
+ }
+ 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;
+ }
+
+ // 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.
+ int64_t presentationDuration =
+ ctsOrder.LastElement()->mCompositionRange.end
+ - ctsOrder[0]->mCompositionRange.start;
+ int64_t endDecodeTime =
+ aMdhd.ToMicroseconds((int64_t)*aDecodeTime - aEdts.mMediaStart)
+ + aMvhd.ToMicroseconds(aEdts.mEmptyOffset);
+ int64_t decodeDuration = endDecodeTime - mIndex[0].mDecodeTime;
+ double adjust = (double)decodeDuration / presentationDuration;
+ int64_t dtsOffset = mIndex[0].mDecodeTime;
+ int64_t compositionDuration = 0;
+ // 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 + int64_t(compositionDuration * adjust);
+ compositionDuration += sample.mCompositionRange.Length();
+ }
+ mTimeRange = Interval<Microseconds>(ctsOrder[0]->mCompositionRange.start,
+ ctsOrder.LastElement()->mCompositionRange.end);
+ }
+ ProcessCenc();
+ }
+}
+
+bool
+Moof::GetAuxInfo(AtomType aType, nsTArray<MediaByteRange>* aByteRanges)
+{
+ aByteRanges->Clear();
+
+ Saiz* saiz = nullptr;
+ for (int i = 0; ; i++) {
+ if (i == mSaizs.Length()) {
+ return false;
+ }
+ if (mSaizs[i].mAuxInfoType == aType) {
+ saiz = &mSaizs[i];
+ break;
+ }
+ }
+ Saio* saio = nullptr;
+ for (int i = 0; ; i++) {
+ if (i == mSaios.Length()) {
+ return false;
+ }
+ if (mSaios[i].mAuxInfoType == aType) {
+ saio = &mSaios[i];
+ break;
+ }
+ }
+
+ if (saio->mOffsets.Length() == 1) {
+ aByteRanges->SetCapacity(saiz->mSampleInfoSize.Length());
+ uint64_t offset = mRange.mStart + saio->mOffsets[0];
+ for (size_t i = 0; i < saiz->mSampleInfoSize.Length(); i++) {
+ aByteRanges->AppendElement(
+ MediaByteRange(offset, offset + saiz->mSampleInfoSize[i]));
+ offset += saiz->mSampleInfoSize[i];
+ }
+ return true;
+ }
+
+ if (saio->mOffsets.Length() == saiz->mSampleInfoSize.Length()) {
+ aByteRanges->SetCapacity(saiz->mSampleInfoSize.Length());
+ for (size_t i = 0; i < saio->mOffsets.Length(); i++) {
+ uint64_t offset = mRange.mStart + saio->mOffsets[i];
+ aByteRanges->AppendElement(
+ MediaByteRange(offset, offset + saiz->mSampleInfoSize[i]));
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool
+Moof::ProcessCenc()
+{
+ nsTArray<MediaByteRange> cencRanges;
+ if (!GetAuxInfo(AtomType("cenc"), &cencRanges) ||
+ cencRanges.Length() != mIndex.Length()) {
+ return false;
+ }
+ for (int i = 0; i < cencRanges.Length(); i++) {
+ mIndex[i].mCencRange = cencRanges[i];
+ }
+ return true;
+}
+
+void
+Moof::ParseTraf(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio)
+{
+ MOZ_ASSERT(aDecodeTime);
+ Tfhd tfhd(aTrex);
+ Tfdt tfdt;
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("tfhd")) {
+ tfhd = Tfhd(box, aTrex);
+ } else if (!aTrex.mTrackId || tfhd.mTrackId == aTrex.mTrackId) {
+ if (box.IsType("tfdt")) {
+ tfdt = Tfdt(box);
+ } else if (box.IsType("saiz")) {
+ mSaizs.AppendElement(Saiz(box, aSinf.mDefaultEncryptionType));
+ } else if (box.IsType("saio")) {
+ mSaios.AppendElement(Saio(box, aSinf.mDefaultEncryptionType));
+ }
+ }
+ }
+ if (aTrex.mTrackId && tfhd.mTrackId != aTrex.mTrackId) {
+ return;
+ }
+ // Now search for TRUN boxes.
+ uint64_t decodeTime =
+ tfdt.IsValid() ? tfdt.mBaseMediaDecodeTime : *aDecodeTime;
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("trun")) {
+ if (ParseTrun(box, tfhd, aMvhd, aMdhd, aEdts, &decodeTime, aIsAudio)) {
+ mValid = true;
+ } else {
+ mValid = false;
+ break;
+ }
+ }
+ }
+ *aDecodeTime = decodeTime;
+}
+
+void
+Moof::FixRounding(const Moof& aMoof) {
+ Microseconds gap = aMoof.mTimeRange.start - mTimeRange.end;
+ if (gap > 0 && gap <= mMaxRoundingError) {
+ mTimeRange.end = aMoof.mTimeRange.start;
+ }
+}
+
+bool
+Moof::ParseTrun(Box& aBox, Tfhd& aTfhd, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, uint64_t* aDecodeTime, bool aIsAudio)
+{
+ if (!aTfhd.IsValid() || !aMvhd.IsValid() || !aMdhd.IsValid() ||
+ !aEdts.IsValid()) {
+ LOG(Moof, "Invalid dependencies: aTfhd(%d) aMvhd(%d) aMdhd(%d) aEdts(%d)",
+ aTfhd.IsValid(), aMvhd.IsValid(), aMdhd.IsValid(), !aEdts.IsValid());
+ return false;
+ }
+
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Moof, "Incomplete Box (missing flags)");
+ return false;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Moof, "Incomplete Box (missing sampleCount)");
+ return false;
+ }
+ uint32_t sampleCount = reader->ReadU32();
+ if (sampleCount == 0) {
+ return true;
+ }
+
+ size_t need =
+ ((flags & 1) ? sizeof(uint32_t) : 0) +
+ ((flags & 4) ? sizeof(uint32_t) : 0);
+ uint16_t flag[] = { 0x100, 0x200, 0x400, 0x800, 0 };
+ for (size_t i = 0; flag[i]; i++) {
+ if (flags & flag[i]) {
+ need += sizeof(uint32_t) * sampleCount;
+ }
+ }
+ if (reader->Remaining() < need) {
+ LOG(Moof, "Incomplete Box (have:%lld need:%lld)",
+ reader->Remaining(), need);
+ return false;
+ }
+
+ uint64_t offset = aTfhd.mBaseDataOffset + (flags & 1 ? reader->ReadU32() : 0);
+ uint32_t firstSampleFlags =
+ flags & 4 ? reader->ReadU32() : aTfhd.mDefaultSampleFlags;
+ uint64_t decodeTime = *aDecodeTime;
+ nsTArray<Interval<Microseconds>> timeRanges;
+
+ if (!mIndex.SetCapacity(sampleCount, fallible)) {
+ LOG(Moof, "Out of Memory");
+ return false;
+ }
+
+ for (size_t i = 0; i < sampleCount; i++) {
+ uint32_t sampleDuration =
+ flags & 0x100 ? reader->ReadU32() : aTfhd.mDefaultSampleDuration;
+ uint32_t sampleSize =
+ flags & 0x200 ? reader->ReadU32() : aTfhd.mDefaultSampleSize;
+ uint32_t sampleFlags =
+ flags & 0x400 ? reader->ReadU32()
+ : i ? aTfhd.mDefaultSampleFlags : firstSampleFlags;
+ int32_t ctsOffset = 0;
+ if (flags & 0x800) {
+ ctsOffset = reader->Read32();
+ }
+
+ Sample sample;
+ sample.mByteRange = MediaByteRange(offset, offset + sampleSize);
+ offset += sampleSize;
+
+ sample.mDecodeTime =
+ aMdhd.ToMicroseconds((int64_t)decodeTime - aEdts.mMediaStart) + aMvhd.ToMicroseconds(aEdts.mEmptyOffset);
+ sample.mCompositionRange = Interval<Microseconds>(
+ aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset - aEdts.mMediaStart) + aMvhd.ToMicroseconds(aEdts.mEmptyOffset),
+ aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset + sampleDuration - aEdts.mMediaStart) + aMvhd.ToMicroseconds(aEdts.mEmptyOffset));
+ decodeTime += sampleDuration;
+
+ // Sometimes audio streams don't properly mark their samples as keyframes,
+ // because every audio sample is a keyframe.
+ sample.mSync = !(sampleFlags & 0x1010000) || aIsAudio;
+
+ // FIXME: Make this infallible after bug 968520 is done.
+ MOZ_ALWAYS_TRUE(mIndex.AppendElement(sample, fallible));
+
+ mMdatRange = mMdatRange.Span(sample.mByteRange);
+ }
+ mMaxRoundingError += aMdhd.ToMicroseconds(sampleCount);
+
+ *aDecodeTime = decodeTime;
+
+ return true;
+}
+
+Tkhd::Tkhd(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Tkhd, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ 3*(version ? sizeof(int64_t) : sizeof(int32_t)) + 2*sizeof(int32_t);
+ if (reader->Remaining() < need) {
+ LOG(Tkhd, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (version == 0) {
+ mCreationTime = reader->ReadU32();
+ mModificationTime = reader->ReadU32();
+ mTrackId = reader->ReadU32();
+ uint32_t reserved = reader->ReadU32();
+ NS_ASSERTION(!reserved, "reserved should be 0");
+ mDuration = reader->ReadU32();
+ } else if (version == 1) {
+ mCreationTime = reader->ReadU64();
+ mModificationTime = reader->ReadU64();
+ mTrackId = reader->ReadU32();
+ uint32_t reserved = reader->ReadU32();
+ NS_ASSERTION(!reserved, "reserved should be 0");
+ mDuration = reader->ReadU64();
+ }
+ // We don't care about whatever else may be in the box.
+ mValid = true;
+}
+
+Mvhd::Mvhd(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Mdhd, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ 3*(version ? sizeof(int64_t) : sizeof(int32_t)) + sizeof(uint32_t);
+ if (reader->Remaining() < need) {
+ LOG(Mvhd, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+
+ if (version == 0) {
+ mCreationTime = reader->ReadU32();
+ mModificationTime = reader->ReadU32();
+ mTimescale = reader->ReadU32();
+ mDuration = reader->ReadU32();
+ } else if (version == 1) {
+ mCreationTime = reader->ReadU64();
+ mModificationTime = reader->ReadU64();
+ mTimescale = reader->ReadU32();
+ mDuration = reader->ReadU64();
+ } else {
+ return;
+ }
+ // We don't care about whatever else may be in the box.
+ if (mTimescale) {
+ mValid = true;
+ }
+}
+
+Mdhd::Mdhd(Box& aBox)
+ : Mvhd(aBox)
+{
+}
+
+Trex::Trex(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (reader->Remaining() < 6*sizeof(uint32_t)) {
+ LOG(Trex, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)6*sizeof(uint32_t));
+ return;
+ }
+ mFlags = reader->ReadU32();
+ mTrackId = reader->ReadU32();
+ mDefaultSampleDescriptionIndex = reader->ReadU32();
+ mDefaultSampleDuration = reader->ReadU32();
+ mDefaultSampleSize = reader->ReadU32();
+ mDefaultSampleFlags = reader->ReadU32();
+ mValid = true;
+}
+
+Tfhd::Tfhd(Box& aBox, Trex& aTrex)
+ : Trex(aTrex)
+{
+ MOZ_ASSERT(aBox.IsType("tfhd"));
+ MOZ_ASSERT(aBox.Parent()->IsType("traf"));
+ MOZ_ASSERT(aBox.Parent()->Parent()->IsType("moof"));
+
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Tfhd, "Incomplete Box (missing flags)");
+ return;
+ }
+ mFlags = reader->ReadU32();
+ size_t need = sizeof(uint32_t) /* trackid */;
+ uint8_t flag[] = { 1, 2, 8, 0x10, 0x20, 0 };
+ uint8_t flagSize[] = { sizeof(uint64_t), sizeof(uint32_t), sizeof(uint32_t), sizeof(uint32_t), sizeof(uint32_t) };
+ for (size_t i = 0; flag[i]; i++) {
+ if (mFlags & flag[i]) {
+ need += flagSize[i];
+ }
+ }
+ if (reader->Remaining() < need) {
+ LOG(Tfhd, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ mTrackId = reader->ReadU32();
+ mBaseDataOffset =
+ mFlags & 1 ? reader->ReadU64() : aBox.Parent()->Parent()->Offset();
+ if (mFlags & 2) {
+ mDefaultSampleDescriptionIndex = reader->ReadU32();
+ }
+ if (mFlags & 8) {
+ mDefaultSampleDuration = reader->ReadU32();
+ }
+ if (mFlags & 0x10) {
+ mDefaultSampleSize = reader->ReadU32();
+ }
+ if (mFlags & 0x20) {
+ mDefaultSampleFlags = reader->ReadU32();
+ }
+ mValid = true;
+}
+
+Tfdt::Tfdt(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Tfdt, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need = version ? sizeof(uint64_t) : sizeof(uint32_t) ;
+ if (reader->Remaining() < need) {
+ LOG(Tfdt, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (version == 0) {
+ mBaseMediaDecodeTime = reader->ReadU32();
+ } else if (version == 1) {
+ mBaseMediaDecodeTime = reader->ReadU64();
+ }
+ mValid = true;
+}
+
+Edts::Edts(Box& aBox)
+ : mMediaStart(0)
+ , mEmptyOffset(0)
+{
+ Box child = aBox.FirstChild();
+ if (!child.IsType("elst")) {
+ return;
+ }
+
+ BoxReader reader(child);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Edts, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ sizeof(uint32_t) + 2*(version ? sizeof(int64_t) : sizeof(uint32_t));
+ if (reader->Remaining() < need) {
+ LOG(Edts, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ bool emptyEntry = false;
+ uint32_t entryCount = reader->ReadU32();
+ for (uint32_t i = 0; i < entryCount; i++) {
+ uint64_t segment_duration;
+ int64_t media_time;
+ if (version == 1) {
+ segment_duration = reader->ReadU64();
+ media_time = reader->Read64();
+ } else {
+ segment_duration = reader->ReadU32();
+ media_time = reader->Read32();
+ }
+ if (media_time == -1 && i) {
+ LOG(Edts, "Multiple empty edit, not handled");
+ } else if (media_time == -1) {
+ mEmptyOffset = segment_duration;
+ emptyEntry = true;
+ } else if (i > 1 || (i > 0 && !emptyEntry)) {
+ LOG(Edts, "More than one edit entry, not handled. A/V sync will be wrong");
+ break;
+ } else {
+ mMediaStart = media_time;
+ }
+ reader->ReadU32(); // media_rate_integer and media_rate_fraction
+ }
+}
+
+Saiz::Saiz(Box& aBox, AtomType aDefaultType)
+ : mAuxInfoType(aDefaultType)
+ , mAuxInfoTypeParameter(0)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Saiz, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ ((flags & 1) ? 2*sizeof(uint32_t) : 0) + sizeof(uint8_t) + sizeof(uint32_t);
+ if (reader->Remaining() < need) {
+ LOG(Saiz, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (flags & 1) {
+ mAuxInfoType = reader->ReadU32();
+ mAuxInfoTypeParameter = reader->ReadU32();
+ }
+ uint8_t defaultSampleInfoSize = reader->ReadU8();
+ uint32_t count = reader->ReadU32();
+ if (defaultSampleInfoSize) {
+ if (!mSampleInfoSize.SetLength(count, fallible)) {
+ LOG(Saiz, "OOM");
+ return;
+ }
+ memset(mSampleInfoSize.Elements(), defaultSampleInfoSize, mSampleInfoSize.Length());
+ } else {
+ if (!reader->ReadArray(mSampleInfoSize, count)) {
+ LOG(Saiz, "Incomplete Box (OOM or missing count:%u)", count);
+ return;
+ }
+ }
+ mValid = true;
+}
+
+Saio::Saio(Box& aBox, AtomType aDefaultType)
+ : mAuxInfoType(aDefaultType)
+ , mAuxInfoTypeParameter(0)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Saio, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need = ((flags & 1) ? (2*sizeof(uint32_t)) : 0) + sizeof(uint32_t);
+ if (reader->Remaining() < need) {
+ LOG(Saio, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (flags & 1) {
+ mAuxInfoType = reader->ReadU32();
+ mAuxInfoTypeParameter = reader->ReadU32();
+ }
+ size_t count = reader->ReadU32();
+ need = (version ? sizeof(uint64_t) : sizeof(uint32_t)) * count;
+ if (reader->Remaining() < need) {
+ LOG(Saio, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (!mOffsets.SetCapacity(count, fallible)) {
+ LOG(Saiz, "OOM");
+ return;
+ }
+ if (version == 0) {
+ for (size_t i = 0; i < count; i++) {
+ MOZ_ALWAYS_TRUE(mOffsets.AppendElement(reader->ReadU32(), fallible));
+ }
+ } else {
+ for (size_t i = 0; i < count; i++) {
+ MOZ_ALWAYS_TRUE(mOffsets.AppendElement(reader->ReadU64(), fallible));
+ }
+ }
+ mValid = true;
+}
+
+#undef LOG
+}
diff --git a/media/libstagefright/binding/ResourceStream.cpp b/media/libstagefright/binding/ResourceStream.cpp
new file mode 100644
index 000000000..2fd042cf4
--- /dev/null
+++ b/media/libstagefright/binding/ResourceStream.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mp4_demuxer/ResourceStream.h"
+
+namespace mp4_demuxer {
+
+ResourceStream::ResourceStream(mozilla::MediaResource* aResource)
+ : mResource(aResource)
+ , mPinCount(0)
+{
+ MOZ_ASSERT(aResource);
+}
+
+ResourceStream::~ResourceStream()
+{
+ MOZ_ASSERT(mPinCount == 0);
+}
+
+bool
+ResourceStream::ReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
+ size_t* aBytesRead)
+{
+ uint32_t sum = 0;
+ uint32_t bytesRead = 0;
+ do {
+ uint64_t offset = aOffset + sum;
+ char* buffer = reinterpret_cast<char*>(aBuffer) + sum;
+ uint32_t toRead = aCount - sum;
+ nsresult rv = mResource->ReadAt(offset, buffer, toRead, &bytesRead);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ sum += bytesRead;
+ } while (sum < aCount && bytesRead > 0);
+
+ *aBytesRead = sum;
+ return true;
+}
+
+bool
+ResourceStream::CachedReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
+ size_t* aBytesRead)
+{
+ nsresult rv = mResource->ReadFromCache(reinterpret_cast<char*>(aBuffer),
+ aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ *aBytesRead = 0;
+ return false;
+ }
+ *aBytesRead = aCount;
+ return true;
+}
+
+bool
+ResourceStream::Length(int64_t* aSize)
+{
+ if (mResource->GetLength() < 0)
+ return false;
+ *aSize = mResource->GetLength();
+ return true;
+}
+
+} // namespace mp4_demuxer
diff --git a/media/libstagefright/binding/SinfParser.cpp b/media/libstagefright/binding/SinfParser.cpp
new file mode 100644
index 000000000..5cf3aa553
--- /dev/null
+++ b/media/libstagefright/binding/SinfParser.cpp
@@ -0,0 +1,72 @@
+/* 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 "mozilla/Unused.h"
+#include "mp4_demuxer/SinfParser.h"
+#include "mp4_demuxer/AtomType.h"
+#include "mp4_demuxer/Box.h"
+
+namespace mp4_demuxer {
+
+Sinf::Sinf(Box& aBox)
+ : mDefaultIVSize(0)
+ , mDefaultEncryptionType()
+{
+ SinfParser parser(aBox);
+ if (parser.GetSinf().IsValid()) {
+ *this = parser.GetSinf();
+ }
+}
+
+SinfParser::SinfParser(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("schm")) {
+ ParseSchm(box);
+ } else if (box.IsType("schi")) {
+ ParseSchi(box);
+ }
+ }
+}
+
+void
+SinfParser::ParseSchm(Box& aBox)
+{
+ BoxReader reader(aBox);
+
+ if (reader->Remaining() < 8) {
+ return;
+ }
+
+ mozilla::Unused << reader->ReadU32(); // flags -- ignore
+ mSinf.mDefaultEncryptionType = reader->ReadU32();
+}
+
+void
+SinfParser::ParseSchi(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("tenc")) {
+ ParseTenc(box);
+ }
+ }
+}
+
+void
+SinfParser::ParseTenc(Box& aBox)
+{
+ BoxReader reader(aBox);
+
+ if (reader->Remaining() < 24) {
+ return;
+ }
+
+ mozilla::Unused << reader->ReadU32(); // flags -- ignore
+
+ uint32_t isEncrypted = reader->ReadU24();
+ mSinf.mDefaultIVSize = reader->ReadU8();
+ memcpy(mSinf.mDefaultKeyID, reader->Read(16), 16);
+}
+
+}
diff --git a/media/libstagefright/binding/include/demuxer/TrackDemuxer.h b/media/libstagefright/binding/include/demuxer/TrackDemuxer.h
new file mode 100644
index 000000000..c3f72648b
--- /dev/null
+++ b/media/libstagefright/binding/include/demuxer/TrackDemuxer.h
@@ -0,0 +1,34 @@
+/* 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/. */
+
+#ifndef TRACK_DEMUXER_H_
+#define TRACK_DEMUXER_H_
+
+template <class T> struct already_AddRefed;
+
+namespace mozilla {
+
+class MediaRawData;
+class MediaByteRange;
+
+class TrackDemuxer {
+public:
+ typedef int64_t Microseconds;
+
+ TrackDemuxer() {}
+ virtual ~TrackDemuxer() {}
+
+ virtual void Seek(Microseconds aTime) = 0;
+
+ // DemuxSample returns nullptr on end of stream or error.
+ virtual already_AddRefed<MediaRawData> DemuxSample() = 0;
+
+ // Returns timestamp of next keyframe, or -1 if demuxer can't
+ // report this.
+ virtual Microseconds GetNextKeyframeTime() = 0;
+};
+
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/Adts.h b/media/libstagefright/binding/include/mp4_demuxer/Adts.h
new file mode 100644
index 000000000..8d03beb4b
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/Adts.h
@@ -0,0 +1,26 @@
+/* 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/. */
+
+#ifndef ADTS_H_
+#define ADTS_H_
+
+#include <stdint.h>
+
+namespace mozilla {
+class MediaRawData;
+}
+
+namespace mp4_demuxer
+{
+
+class Adts
+{
+public:
+ static int8_t GetFrequencyIndex(uint32_t aSamplesPerSecond);
+ static bool ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex,
+ int8_t aProfile, mozilla::MediaRawData* aSample);
+};
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/AnnexB.h b/media/libstagefright/binding/include/mp4_demuxer/AnnexB.h
new file mode 100644
index 000000000..879e40c84
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/AnnexB.h
@@ -0,0 +1,55 @@
+/* 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/. */
+
+#ifndef MP4_DEMUXER_ANNEX_B_H_
+#define MP4_DEMUXER_ANNEX_B_H_
+
+template <class T> struct already_AddRefed;
+
+namespace mozilla {
+class MediaRawData;
+class MediaByteBuffer;
+}
+namespace mp4_demuxer
+{
+class ByteReader;
+
+class AnnexB
+{
+public:
+ // All conversions assume size of NAL length field is 4 bytes.
+ // Convert a sample from AVCC format to Annex B.
+ static bool ConvertSampleToAnnexB(mozilla::MediaRawData* aSample, bool aAddSPS = true);
+ // Convert a sample from Annex B to AVCC.
+ // an AVCC extradata must not be set.
+ static bool ConvertSampleToAVCC(mozilla::MediaRawData* aSample);
+ static bool ConvertSampleTo4BytesAVCC(mozilla::MediaRawData* aSample);
+
+ // Parse an AVCC extradata and construct the Annex B sample header.
+ static already_AddRefed<mozilla::MediaByteBuffer> ConvertExtraDataToAnnexB(
+ const mozilla::MediaByteBuffer* aExtraData);
+ // Extract SPS and PPS NALs from aSample, aSample must be in AVCC format.
+ // If aSample already contains an extradata with an SPS, it will be returned
+ // otherwise the SPS/PPS NALs are searched in-band.
+ static already_AddRefed<mozilla::MediaByteBuffer> ExtractExtraData(
+ const mozilla::MediaRawData* aSample);
+ static bool HasSPS(const mozilla::MediaRawData* aSample);
+ static bool HasSPS(const mozilla::MediaByteBuffer* aExtraData);
+ // Returns true if format is AVCC and sample has valid extradata.
+ static bool IsAVCC(const mozilla::MediaRawData* aSample);
+ // Returns true if format is AnnexB.
+ static bool IsAnnexB(const mozilla::MediaRawData* aSample);
+ // Return true if both extradata are equal.
+ static bool CompareExtraData(const mozilla::MediaByteBuffer* aExtraData1,
+ const mozilla::MediaByteBuffer* aExtraData2);
+
+private:
+ // AVCC box parser helper.
+ static void ConvertSPSOrPPS(ByteReader& aReader, uint8_t aCount,
+ mozilla::MediaByteBuffer* aAnnexB);
+};
+
+} // namespace mp4_demuxer
+
+#endif // MP4_DEMUXER_ANNEX_B_H_
diff --git a/media/libstagefright/binding/include/mp4_demuxer/Atom.h b/media/libstagefright/binding/include/mp4_demuxer/Atom.h
new file mode 100644
index 000000000..48f2878d4
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/Atom.h
@@ -0,0 +1,27 @@
+/* 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/. */
+
+#ifndef ATOM_H_
+#define ATOM_H_
+
+namespace mp4_demuxer {
+
+class Atom
+{
+public:
+ Atom()
+ : mValid(false)
+ {
+ }
+ virtual bool IsValid()
+ {
+ return mValid;
+ }
+protected:
+ bool mValid;
+};
+
+}
+
+#endif // ATOM_H_
diff --git a/media/libstagefright/binding/include/mp4_demuxer/AtomType.h b/media/libstagefright/binding/include/mp4_demuxer/AtomType.h
new file mode 100644
index 000000000..95baedfe7
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/AtomType.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef ATOM_TYPE_H_
+#define ATOM_TYPE_H_
+
+#include <stdint.h>
+#include "mozilla/EndianUtils.h"
+
+using namespace mozilla;
+
+namespace mp4_demuxer {
+
+class AtomType
+{
+public:
+ AtomType() : mType(0) { }
+ MOZ_IMPLICIT AtomType(uint32_t aType) : mType(aType) { }
+ MOZ_IMPLICIT AtomType(const char* aType) : mType(BigEndian::readUint32(aType)) { }
+ bool operator==(const AtomType& aType) const { return mType == aType.mType; }
+ bool operator!() const { return !mType; }
+
+private:
+ uint32_t mType;
+};
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/BitReader.h b/media/libstagefright/binding/include/mp4_demuxer/BitReader.h
new file mode 100644
index 000000000..27a10338f
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/BitReader.h
@@ -0,0 +1,45 @@
+/* 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/. */
+
+#ifndef BIT_READER_H_
+#define BIT_READER_H_
+
+#include "nsAutoPtr.h"
+#include "MediaData.h"
+
+namespace stagefright { class ABitReader; }
+
+namespace mp4_demuxer
+{
+
+class BitReader
+{
+public:
+ explicit BitReader(const mozilla::MediaByteBuffer* aBuffer);
+ BitReader(const uint8_t* aBuffer, size_t aLength);
+ ~BitReader();
+ uint32_t ReadBits(size_t aNum);
+ uint32_t ReadBit() { return ReadBits(1); }
+ uint32_t ReadU32() { return ReadBits(32); }
+ uint64_t ReadU64();
+
+ // Read the UTF-8 sequence and convert it to its 64-bit UCS-4 encoded form.
+ // Return 0xfffffffffffffff if sequence was invalid.
+ uint64_t ReadUTF8();
+ // Read unsigned integer Exp-Golomb-coded.
+ uint32_t ReadUE();
+ // Read signed integer Exp-Golomb-coded.
+ int32_t ReadSE();
+
+ // Return the number of bits parsed so far;
+ size_t BitCount() const;
+
+private:
+ nsAutoPtr<stagefright::ABitReader> mBitReader;
+ const size_t mSize;
+};
+
+} // namespace mp4_demuxer
+
+#endif // BIT_READER_H_ \ No newline at end of file
diff --git a/media/libstagefright/binding/include/mp4_demuxer/Box.h b/media/libstagefright/binding/include/mp4_demuxer/Box.h
new file mode 100644
index 000000000..f53404a1d
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/Box.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef BOX_H_
+#define BOX_H_
+
+#include <stdint.h>
+#include "nsTArray.h"
+#include "MediaResource.h"
+#include "mozilla/EndianUtils.h"
+#include "mp4_demuxer/AtomType.h"
+#include "mp4_demuxer/ByteReader.h"
+
+using namespace mozilla;
+
+namespace mp4_demuxer {
+
+class Stream;
+
+class BoxContext
+{
+public:
+ BoxContext(Stream* aSource, const MediaByteRangeSet& aByteRanges)
+ : mSource(aSource), mByteRanges(aByteRanges)
+ {
+ }
+
+ RefPtr<Stream> mSource;
+ const MediaByteRangeSet& mByteRanges;
+};
+
+class Box
+{
+public:
+ Box(BoxContext* aContext, uint64_t aOffset, const Box* aParent = nullptr);
+ Box();
+
+ bool IsAvailable() const { return !mRange.IsEmpty(); }
+ uint64_t Offset() const { return mRange.mStart; }
+ uint64_t Length() const { return mRange.mEnd - mRange.mStart; }
+ uint64_t NextOffset() const { return mRange.mEnd; }
+ const MediaByteRange& Range() const { return mRange; }
+ const Box* Parent() const { return mParent; }
+ bool IsType(const char* aType) const { return mType == AtomType(aType); }
+
+ Box Next() const;
+ Box FirstChild() const;
+ nsTArray<uint8_t> Read();
+ bool Read(nsTArray<uint8_t>* aDest, const MediaByteRange& aRange);
+
+ static const uint64_t kMAX_BOX_READ;
+
+private:
+ bool Contains(MediaByteRange aRange) const;
+ BoxContext* mContext;
+ mozilla::MediaByteRange mRange;
+ uint64_t mBodyOffset;
+ uint64_t mChildOffset;
+ AtomType mType;
+ const Box* mParent;
+};
+
+// BoxReader takes a copy of a box contents and serves through an AutoByteReader.
+MOZ_RAII
+class BoxReader
+{
+public:
+ explicit BoxReader(Box& aBox)
+ : mBuffer(aBox.Read())
+ , mReader(mBuffer.Elements(), mBuffer.Length())
+ {
+ }
+ ByteReader* operator->() { return &mReader; }
+
+private:
+ nsTArray<uint8_t> mBuffer;
+ ByteReader mReader;
+};
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/BufferStream.h b/media/libstagefright/binding/include/mp4_demuxer/BufferStream.h
new file mode 100644
index 000000000..bb703db5d
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/BufferStream.h
@@ -0,0 +1,46 @@
+/* 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/. */
+
+#ifndef BUFFER_STREAM_H_
+#define BUFFER_STREAM_H_
+
+#include "mp4_demuxer/Stream.h"
+#include "nsTArray.h"
+#include "MediaResource.h"
+
+namespace mozilla {
+class MediaByteBuffer;
+}
+
+namespace mp4_demuxer {
+
+class BufferStream : public Stream
+{
+public:
+ /* BufferStream does not take ownership of aData nor does it make a copy.
+ * Therefore BufferStream shouldn't get used after aData is destroyed.
+ */
+ BufferStream();
+ explicit BufferStream(mozilla::MediaByteBuffer* aBuffer);
+
+ virtual bool ReadAt(int64_t aOffset, void* aData, size_t aLength,
+ size_t* aBytesRead) override;
+ virtual bool CachedReadAt(int64_t aOffset, void* aData, size_t aLength,
+ size_t* aBytesRead) override;
+ virtual bool Length(int64_t* aLength) override;
+
+ virtual void DiscardBefore(int64_t aOffset) override;
+
+ bool AppendBytes(const uint8_t* aData, size_t aLength);
+
+ mozilla::MediaByteRange GetByteRange();
+
+private:
+ ~BufferStream();
+ int64_t mStartOffset;
+ RefPtr<mozilla::MediaByteBuffer> mData;
+};
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/ByteReader.h b/media/libstagefright/binding/include/mp4_demuxer/ByteReader.h
new file mode 100644
index 000000000..9c7df04bd
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/ByteReader.h
@@ -0,0 +1,349 @@
+/* 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/. */
+
+#ifndef BYTE_READER_H_
+#define BYTE_READER_H_
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Vector.h"
+#include "nsTArray.h"
+#include "MediaData.h"
+
+namespace mp4_demuxer {
+
+class MOZ_RAII ByteReader
+{
+public:
+ ByteReader() : mPtr(nullptr), mRemaining(0) {}
+ explicit ByteReader(const mozilla::Vector<uint8_t>& aData)
+ : mPtr(aData.begin()), mRemaining(aData.length()), mLength(aData.length())
+ {
+ }
+ ByteReader(const uint8_t* aData, size_t aSize)
+ : mPtr(aData), mRemaining(aSize), mLength(aSize)
+ {
+ }
+ template<size_t S>
+ explicit ByteReader(const AutoTArray<uint8_t, S>& aData)
+ : mPtr(aData.Elements()), mRemaining(aData.Length()), mLength(aData.Length())
+ {
+ }
+ explicit ByteReader(const nsTArray<uint8_t>& aData)
+ : mPtr(aData.Elements()), mRemaining(aData.Length()), mLength(aData.Length())
+ {
+ }
+ explicit ByteReader(const mozilla::MediaByteBuffer* aData)
+ : mPtr(aData->Elements()), mRemaining(aData->Length()), mLength(aData->Length())
+ {
+ }
+
+ void SetData(const nsTArray<uint8_t>& aData)
+ {
+ MOZ_ASSERT(!mPtr && !mRemaining);
+ mPtr = aData.Elements();
+ mRemaining = aData.Length();
+ mLength = mRemaining;
+ }
+
+ ~ByteReader()
+ {
+ }
+
+ size_t Offset()
+ {
+ return mLength - mRemaining;
+ }
+
+ size_t Remaining() const { return mRemaining; }
+
+ bool CanRead8() { return mRemaining >= 1; }
+
+ uint8_t ReadU8()
+ {
+ auto ptr = Read(1);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return *ptr;
+ }
+
+ bool CanRead16() { return mRemaining >= 2; }
+
+ uint16_t ReadU16()
+ {
+ auto ptr = Read(2);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readUint16(ptr);
+ }
+
+ int16_t ReadLE16()
+ {
+ auto ptr = Read(2);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::LittleEndian::readInt16(ptr);
+ }
+
+ uint32_t ReadU24()
+ {
+ auto ptr = Read(3);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return ptr[0] << 16 | ptr[1] << 8 | ptr[2];
+ }
+
+ uint32_t Read24()
+ {
+ return (uint32_t)ReadU24();
+ }
+
+ int32_t ReadLE24()
+ {
+ auto ptr = Read(3);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ int32_t result = int32_t(ptr[2] << 16 | ptr[1] << 8 | ptr[0]);
+ if (result & 0x00800000u) {
+ result -= 0x1000000;
+ }
+ return result;
+ }
+
+ bool CanRead32() { return mRemaining >= 4; }
+
+ uint32_t ReadU32()
+ {
+ auto ptr = Read(4);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readUint32(ptr);
+ }
+
+ int32_t Read32()
+ {
+ auto ptr = Read(4);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readInt32(ptr);
+ }
+
+ uint64_t ReadU64()
+ {
+ auto ptr = Read(8);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readUint64(ptr);
+ }
+
+ int64_t Read64()
+ {
+ auto ptr = Read(8);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readInt64(ptr);
+ }
+
+ const uint8_t* Read(size_t aCount)
+ {
+ if (aCount > mRemaining) {
+ mRemaining = 0;
+ return nullptr;
+ }
+ mRemaining -= aCount;
+
+ const uint8_t* result = mPtr;
+ mPtr += aCount;
+
+ return result;
+ }
+
+ const uint8_t* Rewind(size_t aCount)
+ {
+ MOZ_ASSERT(aCount <= Offset());
+ size_t rewind = Offset();
+ if (aCount < rewind) {
+ rewind = aCount;
+ }
+ mRemaining += rewind;
+ mPtr -= rewind;
+ return mPtr;
+ }
+
+ uint8_t PeekU8()
+ {
+ auto ptr = Peek(1);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return *ptr;
+ }
+
+ uint16_t PeekU16()
+ {
+ auto ptr = Peek(2);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readUint16(ptr);
+ }
+
+ uint32_t PeekU24()
+ {
+ auto ptr = Peek(3);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return ptr[0] << 16 | ptr[1] << 8 | ptr[2];
+ }
+
+ uint32_t Peek24()
+ {
+ return (uint32_t)PeekU24();
+ }
+
+ uint32_t PeekU32()
+ {
+ auto ptr = Peek(4);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readUint32(ptr);
+ }
+
+ int32_t Peek32()
+ {
+ auto ptr = Peek(4);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readInt32(ptr);
+ }
+
+ uint64_t PeekU64()
+ {
+ auto ptr = Peek(8);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readUint64(ptr);
+ }
+
+ int64_t Peek64()
+ {
+ auto ptr = Peek(8);
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return mozilla::BigEndian::readInt64(ptr);
+ }
+
+ const uint8_t* Peek(size_t aCount)
+ {
+ if (aCount > mRemaining) {
+ return nullptr;
+ }
+ return mPtr;
+ }
+
+ const uint8_t* Seek(size_t aOffset)
+ {
+ if (aOffset >= mLength) {
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+
+ mPtr = mPtr - Offset() + aOffset;
+ mRemaining = mLength - aOffset;
+ return mPtr;
+ }
+
+ const uint8_t* Reset()
+ {
+ mPtr -= Offset();
+ mRemaining = mLength;
+ return mPtr;
+ }
+
+ uint32_t Align()
+ {
+ return 4 - ((intptr_t)mPtr & 3);
+ }
+
+ template <typename T> bool CanReadType() { return mRemaining >= sizeof(T); }
+
+ template <typename T> T ReadType()
+ {
+ auto ptr = Read(sizeof(T));
+ if (!ptr) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return *reinterpret_cast<const T*>(ptr);
+ }
+
+ template <typename T>
+ MOZ_MUST_USE bool ReadArray(nsTArray<T>& aDest, size_t aLength)
+ {
+ auto ptr = Read(aLength * sizeof(T));
+ if (!ptr) {
+ return false;
+ }
+
+ aDest.Clear();
+ aDest.AppendElements(reinterpret_cast<const T*>(ptr), aLength);
+ return true;
+ }
+
+ template <typename T>
+ MOZ_MUST_USE bool ReadArray(FallibleTArray<T>& aDest, size_t aLength)
+ {
+ auto ptr = Read(aLength * sizeof(T));
+ if (!ptr) {
+ return false;
+ }
+
+ aDest.Clear();
+ if (!aDest.SetCapacity(aLength, mozilla::fallible)) {
+ return false;
+ }
+ MOZ_ALWAYS_TRUE(aDest.AppendElements(reinterpret_cast<const T*>(ptr),
+ aLength,
+ mozilla::fallible));
+ return true;
+ }
+
+private:
+ const uint8_t* mPtr;
+ size_t mRemaining;
+ size_t mLength;
+};
+
+} // namespace mp4_demuxer
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/ByteWriter.h b/media/libstagefright/binding/include/mp4_demuxer/ByteWriter.h
new file mode 100644
index 000000000..48ebdd460
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/ByteWriter.h
@@ -0,0 +1,76 @@
+/* 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/. */
+
+#ifndef BYTE_WRITER_H_
+#define BYTE_WRITER_H_
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Vector.h"
+#include "nsTArray.h"
+
+namespace mp4_demuxer {
+
+class ByteWriter
+{
+public:
+ explicit ByteWriter(mozilla::Vector<uint8_t>& aData)
+ : mPtr(aData)
+ {
+ }
+ ~ByteWriter()
+ {
+ }
+
+ MOZ_MUST_USE bool WriteU8(uint8_t aByte)
+ {
+ return Write(&aByte, 1);
+ }
+
+ MOZ_MUST_USE bool WriteU16(uint16_t aShort)
+ {
+ uint8_t c[2];
+ mozilla::BigEndian::writeUint16(&c[0], aShort);
+ return Write(&c[0], 2);
+ }
+
+ MOZ_MUST_USE bool WriteU32(uint32_t aLong)
+ {
+ uint8_t c[4];
+ mozilla::BigEndian::writeUint32(&c[0], aLong);
+ return Write(&c[0], 4);
+ }
+
+ MOZ_MUST_USE bool Write32(int32_t aLong)
+ {
+ uint8_t c[4];
+ mozilla::BigEndian::writeInt32(&c[0], aLong);
+ return Write(&c[0], 4);
+ }
+
+ MOZ_MUST_USE bool WriteU64(uint64_t aLongLong)
+ {
+ uint8_t c[8];
+ mozilla::BigEndian::writeUint64(&c[0], aLongLong);
+ return Write(&c[0], 8);
+ }
+
+ MOZ_MUST_USE bool Write64(int64_t aLongLong)
+ {
+ uint8_t c[8];
+ mozilla::BigEndian::writeInt64(&c[0], aLongLong);
+ return Write(&c[0], 8);
+ }
+
+ MOZ_MUST_USE bool Write(const uint8_t* aSrc, size_t aCount)
+ {
+ return mPtr.append(aSrc, aCount);
+ }
+
+private:
+ mozilla::Vector<uint8_t>& mPtr;
+};
+
+} // namespace mp4_demuxer
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/DecoderData.h b/media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
new file mode 100644
index 000000000..87262c26a
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
@@ -0,0 +1,93 @@
+/* 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/. */
+
+#ifndef DECODER_DATA_H_
+#define DECODER_DATA_H_
+
+#include "MediaData.h"
+#include "MediaInfo.h"
+#include "mozilla/Types.h"
+#include "mozilla/Vector.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace stagefright
+{
+class MetaData;
+}
+
+#ifdef MOZ_RUST_MP4PARSE
+extern "C" {
+typedef struct mp4parse_track_info mp4parse_track_info;
+typedef struct mp4parse_track_audio_info mp4parse_track_audio_info;
+typedef struct mp4parse_track_video_info mp4parse_track_video_info;
+}
+#endif
+
+namespace mp4_demuxer
+{
+
+class MP4Demuxer;
+
+struct PsshInfo
+{
+ PsshInfo() {}
+ PsshInfo(const PsshInfo& aOther) : uuid(aOther.uuid), data(aOther.data) {}
+ nsTArray<uint8_t> uuid;
+ nsTArray<uint8_t> data;
+};
+
+class CryptoFile
+{
+public:
+ CryptoFile() : valid(false) {}
+ CryptoFile(const CryptoFile& aCryptoFile) : valid(aCryptoFile.valid)
+ {
+ pssh.AppendElements(aCryptoFile.pssh);
+ }
+
+ void Update(const uint8_t* aData, size_t aLength)
+ {
+ valid = DoUpdate(aData, aLength);
+ }
+
+ bool valid;
+ nsTArray<PsshInfo> pssh;
+
+private:
+ bool DoUpdate(const uint8_t* aData, size_t aLength);
+};
+
+class MP4AudioInfo : public mozilla::AudioInfo
+{
+public:
+ MP4AudioInfo() = default;
+
+ void Update(const stagefright::MetaData* aMetaData,
+ const char* aMimeType);
+
+ virtual bool IsValid() const override;
+};
+
+class MP4VideoInfo : public mozilla::VideoInfo
+{
+public:
+ MP4VideoInfo() = default;
+
+ void Update(const stagefright::MetaData* aMetaData,
+ const char* aMimeType);
+
+#ifdef MOZ_RUST_MP4PARSE
+ void Update(const mp4parse_track_info* track,
+ const mp4parse_track_video_info* video);
+#endif
+
+ virtual bool IsValid() const override;
+};
+
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/H264.h b/media/libstagefright/binding/include/mp4_demuxer/H264.h
new file mode 100644
index 000000000..2aa710f61
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/H264.h
@@ -0,0 +1,372 @@
+/* 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/. */
+
+#ifndef MP4_DEMUXER_H264_H_
+#define MP4_DEMUXER_H264_H_
+
+#include "mp4_demuxer/DecoderData.h"
+
+namespace mp4_demuxer
+{
+
+class BitReader;
+
+struct SPSData
+{
+ /* Decoded Members */
+ /*
+ pic_width is the decoded width according to:
+ pic_width = ((pic_width_in_mbs_minus1 + 1) * 16)
+ - (frame_crop_left_offset + frame_crop_right_offset) * 2
+ */
+ uint32_t pic_width;
+ /*
+ pic_height is the decoded height according to:
+ pic_height = (2 - frame_mbs_only_flag) * ((pic_height_in_map_units_minus1 + 1) * 16)
+ - (frame_crop_top_offset + frame_crop_bottom_offset) * 2
+ */
+ uint32_t pic_height;
+
+ bool interlaced;
+
+ /*
+ Displayed size.
+ display_width and display_height are adjusted according to the display
+ sample aspect ratio.
+ */
+ uint32_t display_width;
+ uint32_t display_height;
+
+ float sample_ratio;
+
+ uint32_t crop_left;
+ uint32_t crop_right;
+ uint32_t crop_top;
+ uint32_t crop_bottom;
+
+ /*
+ H264 decoding parameters according to ITU-T H.264 (T-REC-H.264-201402-I/en)
+ http://www.itu.int/rec/T-REC-H.264-201402-I/en
+ */
+
+ bool constraint_set0_flag;
+ bool constraint_set1_flag;
+ bool constraint_set2_flag;
+ bool constraint_set3_flag;
+ bool constraint_set4_flag;
+ bool constraint_set5_flag;
+
+ /*
+ profile_idc and level_idc indicate the profile and level to which the coded
+ video sequence conforms when the SVC sequence parameter set is the active
+ SVC sequence parameter set.
+ */
+ uint8_t profile_idc;
+ uint8_t level_idc;
+
+ /*
+ seq_parameter_set_id identifies the sequence parameter set that is referred
+ to by the picture parameter set. The value of seq_parameter_set_id shall be
+ in the range of 0 to 31, inclusive.
+ */
+ uint8_t seq_parameter_set_id;
+
+ /*
+ chroma_format_idc specifies the chroma sampling relative to the luma
+ sampling as specified in clause 6.2. The value of chroma_format_idc shall be
+ in the range of 0 to 3, inclusive. When chroma_format_idc is not present,
+ it shall be inferred to be equal to 1 (4:2:0 chroma format).
+ When profile_idc is equal to 183, chroma_format_idc shall be equal to 0
+ (4:0:0 chroma format).
+ */
+ uint8_t chroma_format_idc;
+
+ /*
+ separate_colour_plane_flag equal to 1 specifies that the three colour
+ components of the 4:4:4 chroma format are coded separately.
+ separate_colour_plane_flag equal to 0 specifies that the colour components
+ are not coded separately. When separate_colour_plane_flag is not present,
+ it shall be inferred to be equal to 0. When separate_colour_plane_flag is
+ equal to 1, the primary coded picture consists of three separate components,
+ each of which consists of coded samples of one colour plane (Y, Cb or Cr)
+ that each use the monochrome coding syntax. In this case, each colour plane
+ is associated with a specific colour_plane_id value.
+ */
+ bool separate_colour_plane_flag;
+
+ /*
+ log2_max_frame_num_minus4 specifies the value of the variable
+ MaxFrameNum that is used in frame_num related derivations as
+ follows:
+
+ MaxFrameNum = 2( log2_max_frame_num_minus4 + 4 ). The value of
+ log2_max_frame_num_minus4 shall be in the range of 0 to 12, inclusive.
+ */
+ uint8_t log2_max_frame_num;
+
+ /*
+ pic_order_cnt_type specifies the method to decode picture order
+ count (as specified in subclause 8.2.1). The value of
+ pic_order_cnt_type shall be in the range of 0 to 2, inclusive.
+ */
+ uint8_t pic_order_cnt_type;
+
+ /*
+ log2_max_pic_order_cnt_lsb_minus4 specifies the value of the
+ variable MaxPicOrderCntLsb that is used in the decoding
+ process for picture order count as specified in subclause
+ 8.2.1 as follows:
+
+ MaxPicOrderCntLsb = 2( log2_max_pic_order_cnt_lsb_minus4 + 4 )
+
+ The value of log2_max_pic_order_cnt_lsb_minus4 shall be in
+ the range of 0 to 12, inclusive.
+ */
+ uint8_t log2_max_pic_order_cnt_lsb;
+
+ /*
+ delta_pic_order_always_zero_flag equal to 1 specifies that
+ delta_pic_order_cnt[ 0 ] and delta_pic_order_cnt[ 1 ] are
+ not present in the slice headers of the sequence and shall
+ be inferred to be equal to 0.
+ */
+ bool delta_pic_order_always_zero_flag;
+
+ /*
+ offset_for_non_ref_pic is used to calculate the picture
+ order count of a non-reference picture as specified in
+ 8.2.1. The value of offset_for_non_ref_pic shall be in the
+ range of -231 to 231 - 1, inclusive.
+ */
+ int8_t offset_for_non_ref_pic;
+
+ /*
+ offset_for_top_to_bottom_field is used to calculate the
+ picture order count of a bottom field as specified in
+ subclause 8.2.1. The value of offset_for_top_to_bottom_field
+ shall be in the range of -231 to 231 - 1, inclusive.
+ */
+ int8_t offset_for_top_to_bottom_field;
+
+ /*
+ max_num_ref_frames specifies the maximum number of short-term and
+ long-term reference frames, complementary reference field pairs,
+ and non-paired reference fields that may be used by the decoding
+ process for inter prediction of any picture in the
+ sequence. max_num_ref_frames also determines the size of the sliding
+ window operation as specified in subclause 8.2.5.3. The value of
+ max_num_ref_frames shall be in the range of 0 to MaxDpbSize (as
+ specified in subclause A.3.1 or A.3.2), inclusive.
+ */
+ uint32_t max_num_ref_frames;
+
+ /*
+ gaps_in_frame_num_value_allowed_flag specifies the allowed
+ values of frame_num as specified in subclause 7.4.3 and the
+ decoding process in case of an inferred gap between values of
+ frame_num as specified in subclause 8.2.5.2.
+ */
+ bool gaps_in_frame_num_allowed_flag;
+
+ /*
+ pic_width_in_mbs_minus1 plus 1 specifies the width of each
+ decoded picture in units of macroblocks. 16 macroblocks in a row
+ */
+ uint32_t pic_width_in_mbs;
+
+ /*
+ pic_height_in_map_units_minus1 plus 1 specifies the height in
+ slice group map units of a decoded frame or field. 16
+ macroblocks in each column.
+ */
+ uint32_t pic_height_in_map_units;
+
+ /*
+ frame_mbs_only_flag equal to 0 specifies that coded pictures of
+ the coded video sequence may either be coded fields or coded
+ frames. frame_mbs_only_flag equal to 1 specifies that every
+ coded picture of the coded video sequence is a coded frame
+ containing only frame macroblocks.
+ */
+ bool frame_mbs_only_flag;
+
+ /*
+ mb_adaptive_frame_field_flag equal to 0 specifies no
+ switching between frame and field macroblocks within a
+ picture. mb_adaptive_frame_field_flag equal to 1 specifies
+ the possible use of switching between frame and field
+ macroblocks within frames. When mb_adaptive_frame_field_flag
+ is not present, it shall be inferred to be equal to 0.
+ */
+ bool mb_adaptive_frame_field_flag;
+
+ /*
+ frame_cropping_flag equal to 1 specifies that the frame cropping
+ offset parameters follow next in the sequence parameter
+ set. frame_cropping_flag equal to 0 specifies that the frame
+ cropping offset parameters are not present.
+ */
+ bool frame_cropping_flag;
+ uint32_t frame_crop_left_offset;;
+ uint32_t frame_crop_right_offset;
+ uint32_t frame_crop_top_offset;
+ uint32_t frame_crop_bottom_offset;
+
+ // VUI Parameters
+
+ /*
+ vui_parameters_present_flag equal to 1 specifies that the
+ vui_parameters( ) syntax structure as specified in Annex E is
+ present. vui_parameters_present_flag equal to 0 specifies that
+ the vui_parameters( ) syntax structure as specified in Annex E
+ is not present.
+ */
+ bool vui_parameters_present_flag;
+
+ /*
+ aspect_ratio_info_present_flag equal to 1 specifies that
+ aspect_ratio_idc is present. aspect_ratio_info_present_flag
+ equal to 0 specifies that aspect_ratio_idc is not present.
+ */
+ bool aspect_ratio_info_present_flag;
+
+ /*
+ aspect_ratio_idc specifies the value of the sample aspect
+ ratio of the luma samples. Table E-1 shows the meaning of
+ the code. When aspect_ratio_idc indicates Extended_SAR, the
+ sample aspect ratio is represented by sar_width and
+ sar_height. When the aspect_ratio_idc syntax element is not
+ present, aspect_ratio_idc value shall be inferred to be
+ equal to 0.
+ */
+ uint8_t aspect_ratio_idc;
+ uint32_t sar_width;
+ uint32_t sar_height;
+
+ /*
+ video_signal_type_present_flag equal to 1 specifies that video_format,
+ video_full_range_flag and colour_description_present_flag are present.
+ video_signal_type_present_flag equal to 0, specify that video_format,
+ video_full_range_flag and colour_description_present_flag are not present.
+ */
+ bool video_signal_type_present_flag;
+
+ /*
+ overscan_info_present_flag equal to1 specifies that the
+ overscan_appropriate_flag is present. When overscan_info_present_flag is
+ equal to 0 or is not present, the preferred display method for the video
+ signal is unspecified (Unspecified).
+ */
+ bool overscan_info_present_flag;
+ /*
+ overscan_appropriate_flag equal to 1 indicates that the cropped decoded
+ pictures output are suitable for display using overscan.
+ overscan_appropriate_flag equal to 0 indicates that the cropped decoded
+ pictures output contain visually important information in the entire region
+ out to the edges of the cropping rectangle of the picture
+ */
+ bool overscan_appropriate_flag;
+
+ /*
+ video_format indicates the representation of the pictures as specified in
+ Table E-2, before being coded in accordance with this
+ Recommendation | International Standard. When the video_format syntax element
+ is not present, video_format value shall be inferred to be equal to 5.
+ (Unspecified video format)
+ */
+ uint8_t video_format;
+
+ /*
+ video_full_range_flag indicates the black level and range of the luma and
+ chroma signals as derived from E′Y, E′PB, and E′PR or E′R, E′G, and E′B
+ real-valued component signals.
+ When the video_full_range_flag syntax element is not present, the value of
+ video_full_range_flag shall be inferred to be equal to 0.
+ */
+ bool video_full_range_flag;
+
+ /*
+ colour_description_present_flag equal to1 specifies that colour_primaries,
+ transfer_characteristics and matrix_coefficients are present.
+ colour_description_present_flag equal to 0 specifies that colour_primaries,
+ transfer_characteristics and matrix_coefficients are not present.
+ */
+ bool colour_description_present_flag;
+
+ /*
+ colour_primaries indicates the chromaticity coordinates of the source
+ primaries as specified in Table E-3 in terms of the CIE 1931 definition of
+ x and y as specified by ISO 11664-1.
+ When the colour_primaries syntax element is not present, the value of
+ colour_primaries shall be inferred to be equal to 2 (the chromaticity is
+ unspecified or is determined by the application).
+ */
+ uint8_t colour_primaries;
+
+ /*
+ transfer_characteristics indicates the opto-electronic transfer
+ characteristic of the source picture as specified in Table E-4 as a function
+ of a linear optical intensity input Lc with a nominal real-valued range of 0
+ to 1.
+ When the transfer_characteristics syntax element is not present, the value
+ of transfer_characteristics shall be inferred to be equal to 2
+ (the transfer characteristics are unspecified or are determined by the
+ application).
+ */
+ uint8_t transfer_characteristics;
+
+ uint8_t matrix_coefficients;
+ bool chroma_loc_info_present_flag;
+ uint32_t chroma_sample_loc_type_top_field;
+ uint32_t chroma_sample_loc_type_bottom_field;
+ bool timing_info_present_flag;
+ uint32_t num_units_in_tick;
+ uint32_t time_scale;
+ bool fixed_frame_rate_flag;
+
+ SPSData();
+};
+
+class H264
+{
+public:
+ static bool DecodeSPSFromExtraData(const mozilla::MediaByteBuffer* aExtraData, SPSData& aDest);
+
+ /* Extract RAW BYTE SEQUENCE PAYLOAD from NAL content.
+ Returns nullptr if invalid content.
+ This is compliant to ITU H.264 7.3.1 Syntax in tabular form NAL unit syntax
+ */
+ static already_AddRefed<mozilla::MediaByteBuffer> DecodeNALUnit(const mozilla::MediaByteBuffer* aNAL);
+
+ /* Decode SPS NAL RBSP and fill SPSData structure */
+ static bool DecodeSPS(const mozilla::MediaByteBuffer* aSPS, SPSData& aDest);
+
+ // Ensure that SPS data makes sense, Return true if SPS data was, and false
+ // otherwise. If false, then content will be adjusted accordingly.
+ static bool EnsureSPSIsSane(SPSData& aSPS);
+
+ // If the given aExtraData is valid, return the aExtraData.max_num_ref_frames
+ // clamped to be in the range of [4, 16]; otherwise return 4.
+ static uint32_t ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData);
+
+ enum class FrameType
+ {
+ I_FRAME,
+ OTHER,
+ INVALID,
+ };
+
+ // Returns the frame type. Returns I_FRAME if the sample is an IDR
+ // (Instantaneous Decoding Refresh) Picture.
+ static FrameType GetFrameType(const mozilla::MediaRawData* aSample);
+
+private:
+ static void vui_parameters(BitReader& aBr, SPSData& aDest);
+ // Read HRD parameters, all data is ignored.
+ static void hrd_parameters(BitReader& aBr);
+};
+
+} // namespace mp4_demuxer
+
+#endif // MP4_DEMUXER_H264_H_
diff --git a/media/libstagefright/binding/include/mp4_demuxer/Index.h b/media/libstagefright/binding/include/mp4_demuxer/Index.h
new file mode 100644
index 000000000..d566c9459
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/Index.h
@@ -0,0 +1,128 @@
+/* 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/. */
+
+#ifndef INDEX_H_
+#define INDEX_H_
+
+#include "MediaData.h"
+#include "MediaResource.h"
+#include "TimeUnits.h"
+#include "mp4_demuxer/MoofParser.h"
+#include "mp4_demuxer/Interval.h"
+#include "mp4_demuxer/Stream.h"
+#include "nsISupportsImpl.h"
+
+template<class T> class nsAutoPtr;
+
+namespace mp4_demuxer
+{
+
+class Index;
+
+typedef int64_t Microseconds;
+
+class SampleIterator
+{
+public:
+ explicit SampleIterator(Index* aIndex);
+ ~SampleIterator();
+ already_AddRefed<mozilla::MediaRawData> GetNext();
+ void Seek(Microseconds aTime);
+ Microseconds GetNextKeyframeTime();
+private:
+ Sample* Get();
+ void Next();
+ RefPtr<Index> mIndex;
+ friend class Index;
+ size_t mCurrentMoof;
+ size_t mCurrentSample;
+};
+
+class Index
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Index)
+
+ struct Indice
+ {
+ uint64_t start_offset;
+ uint64_t end_offset;
+ uint64_t start_composition;
+ uint64_t end_composition;
+ uint64_t start_decode;
+ bool sync;
+ };
+
+ struct MP4DataOffset
+ {
+ MP4DataOffset(uint32_t aIndex, int64_t aStartOffset)
+ : mIndex(aIndex)
+ , mStartOffset(aStartOffset)
+ , mEndOffset(0)
+ {}
+
+ bool operator==(int64_t aStartOffset) const {
+ return mStartOffset == aStartOffset;
+ }
+
+ bool operator!=(int64_t aStartOffset) const {
+ return mStartOffset != aStartOffset;
+ }
+
+ bool operator<(int64_t aStartOffset) const {
+ return mStartOffset < aStartOffset;
+ }
+
+ struct EndOffsetComparator {
+ bool Equals(const MP4DataOffset& a, const int64_t& b) const {
+ return a.mEndOffset == b;
+ }
+
+ bool LessThan(const MP4DataOffset& a, const int64_t& b) const {
+ return a.mEndOffset < b;
+ }
+ };
+
+ uint32_t mIndex;
+ int64_t mStartOffset;
+ int64_t mEndOffset;
+ Interval<Microseconds> mTime;
+ };
+
+ Index(const nsTArray<Indice>& aIndex,
+ Stream* aSource,
+ uint32_t aTrackId,
+ bool aIsAudio);
+
+ void UpdateMoofIndex(const mozilla::MediaByteRangeSet& aByteRanges,
+ bool aCanEvict);
+ void UpdateMoofIndex(const mozilla::MediaByteRangeSet& aByteRanges);
+ Microseconds GetEndCompositionIfBuffered(
+ const mozilla::MediaByteRangeSet& aByteRanges);
+ mozilla::media::TimeIntervals ConvertByteRangesToTimeRanges(
+ const mozilla::MediaByteRangeSet& aByteRanges);
+ uint64_t GetEvictionOffset(Microseconds aTime);
+ bool IsFragmented() { return mMoofParser; }
+
+ friend class SampleIterator;
+
+private:
+ ~Index();
+ void RegisterIterator(SampleIterator* aIterator);
+ void UnregisterIterator(SampleIterator* aIterator);
+
+ Stream* mSource;
+ FallibleTArray<Sample> mIndex;
+ FallibleTArray<MP4DataOffset> mDataOffset;
+ nsAutoPtr<MoofParser> mMoofParser;
+ nsTArray<SampleIterator*> mIterators;
+
+ // ConvertByteRangesToTimeRanges cache
+ mozilla::MediaByteRangeSet mLastCachedRanges;
+ mozilla::media::TimeIntervals mLastBufferedRanges;
+ bool mIsAudio;
+};
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/Interval.h b/media/libstagefright/binding/include/mp4_demuxer/Interval.h
new file mode 100644
index 000000000..2c3d0f8af
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/Interval.h
@@ -0,0 +1,147 @@
+/* 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/. */
+
+#ifndef INTERVAL_H_
+#define INTERVAL_H_
+
+#include "nsTArray.h"
+#include <algorithm>
+
+namespace mp4_demuxer
+{
+
+template <typename T>
+struct Interval
+{
+ Interval() : start(0), end(0) {}
+ Interval(T aStart, T aEnd) : start(aStart), end(aEnd)
+ {
+ MOZ_ASSERT(aStart <= aEnd);
+ }
+ T Length() { return end - start; }
+ Interval Intersection(const Interval& aOther) const
+ {
+ T s = start > aOther.start ? start : aOther.start;
+ T e = end < aOther.end ? end : aOther.end;
+ if (s > e) {
+ return Interval();
+ }
+ return Interval(s, e);
+ }
+ bool Contains(const Interval& aOther) const
+ {
+ return aOther.start >= start && aOther.end <= end;
+ }
+ bool operator==(const Interval& aOther) const
+ {
+ return start == aOther.start && end == aOther.end;
+ }
+ bool operator!=(const Interval& aOther) const { return !(*this == aOther); }
+ bool IsNull() const
+ {
+ return end == start;
+ }
+ Interval Extents(const Interval& aOther) const
+ {
+ if (IsNull()) {
+ return aOther;
+ }
+ return Interval(std::min(start, aOther.start),
+ std::max(end, aOther.end));
+ }
+
+ T start;
+ T end;
+
+ static void SemiNormalAppend(nsTArray<Interval<T>>& aIntervals,
+ Interval<T> aInterval)
+ {
+ if (!aIntervals.IsEmpty() &&
+ aIntervals.LastElement().end == aInterval.start) {
+ aIntervals.LastElement().end = aInterval.end;
+ } else {
+ aIntervals.AppendElement(aInterval);
+ }
+ }
+
+ static void Normalize(const nsTArray<Interval<T>>& aIntervals,
+ nsTArray<Interval<T>>* aNormalized)
+ {
+ if (!aNormalized || !aIntervals.Length()) {
+ MOZ_ASSERT(aNormalized);
+ return;
+ }
+ MOZ_ASSERT(aNormalized->IsEmpty());
+
+ nsTArray<Interval<T>> sorted;
+ sorted = aIntervals;
+ sorted.Sort(Compare());
+
+ Interval<T> current = sorted[0];
+ for (size_t i = 1; i < sorted.Length(); i++) {
+ MOZ_ASSERT(sorted[i].start <= sorted[i].end);
+ if (current.Contains(sorted[i])) {
+ continue;
+ }
+ if (current.end >= sorted[i].start) {
+ current.end = sorted[i].end;
+ } else {
+ aNormalized->AppendElement(current);
+ current = sorted[i];
+ }
+ }
+ aNormalized->AppendElement(current);
+ }
+
+ static void Intersection(const nsTArray<Interval<T>>& a0,
+ const nsTArray<Interval<T>>& a1,
+ nsTArray<Interval<T>>* aIntersection)
+ {
+ MOZ_ASSERT(IsNormalized(a0));
+ MOZ_ASSERT(IsNormalized(a1));
+ size_t i0 = 0;
+ size_t i1 = 0;
+ while (i0 < a0.Length() && i1 < a1.Length()) {
+ Interval i = a0[i0].Intersection(a1[i1]);
+ if (i.Length()) {
+ aIntersection->AppendElement(i);
+ }
+ if (a0[i0].end < a1[i1].end) {
+ i0++;
+ // Assert that the array is sorted
+ MOZ_ASSERT(i0 == a0.Length() || a0[i0 - 1].start < a0[i0].start);
+ } else {
+ i1++;
+ // Assert that the array is sorted
+ MOZ_ASSERT(i1 == a1.Length() || a1[i1 - 1].start < a1[i1].start);
+ }
+ }
+ }
+
+ static bool IsNormalized(const nsTArray<Interval<T>>& aIntervals)
+ {
+ for (size_t i = 1; i < aIntervals.Length(); i++) {
+ if (aIntervals[i - 1].end >= aIntervals[i].start) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ struct Compare
+ {
+ bool Equals(const Interval<T>& a0, const Interval<T>& a1) const
+ {
+ return a0.start == a1.start && a0.end == a1.end;
+ }
+
+ bool LessThan(const Interval<T>& a0, const Interval<T>& a1) const
+ {
+ return a0.start < a1.start;
+ }
+ };
+};
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/MP4Metadata.h b/media/libstagefright/binding/include/mp4_demuxer/MP4Metadata.h
new file mode 100644
index 000000000..83eca69d3
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/MP4Metadata.h
@@ -0,0 +1,54 @@
+/* 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/. */
+
+#ifndef MP4METADATA_H_
+#define MP4METADATA_H_
+
+#include "mozilla/UniquePtr.h"
+#include "mp4_demuxer/DecoderData.h"
+#include "mp4_demuxer/Index.h"
+#include "MediaData.h"
+#include "MediaInfo.h"
+#include "Stream.h"
+
+namespace mp4_demuxer
+{
+
+class MP4MetadataStagefright;
+class MP4MetadataRust;
+
+class MP4Metadata
+{
+public:
+ explicit MP4Metadata(Stream* aSource);
+ ~MP4Metadata();
+
+ static bool HasCompleteMetadata(Stream* aSource);
+ static already_AddRefed<mozilla::MediaByteBuffer> Metadata(Stream* aSource);
+ uint32_t GetNumberTracks(mozilla::TrackInfo::TrackType aType) const;
+ mozilla::UniquePtr<mozilla::TrackInfo> GetTrackInfo(mozilla::TrackInfo::TrackType aType,
+ size_t aTrackNumber) const;
+ bool CanSeek() const;
+
+ const CryptoFile& Crypto() const;
+
+ bool ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID);
+
+private:
+ UniquePtr<MP4MetadataStagefright> mStagefright;
+#ifdef MOZ_RUST_MP4PARSE
+ UniquePtr<MP4MetadataRust> mRust;
+ mutable bool mPreferRust;
+ mutable bool mReportedAudioTrackTelemetry;
+ mutable bool mReportedVideoTrackTelemetry;
+#ifndef RELEASE_OR_BETA
+ mutable bool mRustTestMode;
+#endif
+ bool ShouldPreferRust() const;
+#endif
+};
+
+} // namespace mp4_demuxer
+
+#endif // MP4METADATA_H_
diff --git a/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h b/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h
new file mode 100644
index 000000000..814f806fc
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h
@@ -0,0 +1,260 @@
+/* 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/. */
+
+#ifndef MOOF_PARSER_H_
+#define MOOF_PARSER_H_
+
+#include "mp4_demuxer/Atom.h"
+#include "mp4_demuxer/AtomType.h"
+#include "mp4_demuxer/SinfParser.h"
+#include "mp4_demuxer/Stream.h"
+#include "mp4_demuxer/Interval.h"
+#include "MediaResource.h"
+
+namespace mp4_demuxer {
+typedef int64_t Microseconds;
+
+class Box;
+class BoxContext;
+class Moof;
+
+class Mvhd : public Atom
+{
+public:
+ Mvhd()
+ : mCreationTime(0)
+ , mModificationTime(0)
+ , mTimescale(0)
+ , mDuration(0)
+ {
+ }
+ explicit Mvhd(Box& aBox);
+
+ Microseconds ToMicroseconds(int64_t aTimescaleUnits)
+ {
+ int64_t major = aTimescaleUnits / mTimescale;
+ int64_t remainder = aTimescaleUnits % mTimescale;
+ return major * 1000000ll + remainder * 1000000ll / mTimescale;
+ }
+
+ uint64_t mCreationTime;
+ uint64_t mModificationTime;
+ uint32_t mTimescale;
+ uint64_t mDuration;
+};
+
+class Tkhd : public Mvhd
+{
+public:
+ Tkhd()
+ : mTrackId(0)
+ {
+ }
+ explicit Tkhd(Box& aBox);
+
+ uint32_t mTrackId;
+};
+
+class Mdhd : public Mvhd
+{
+public:
+ Mdhd() = default;
+ explicit Mdhd(Box& aBox);
+};
+
+class Trex : public Atom
+{
+public:
+ explicit Trex(uint32_t aTrackId)
+ : mFlags(0)
+ , mTrackId(aTrackId)
+ , mDefaultSampleDescriptionIndex(0)
+ , mDefaultSampleDuration(0)
+ , mDefaultSampleSize(0)
+ , mDefaultSampleFlags(0)
+ {
+ }
+
+ explicit Trex(Box& aBox);
+
+ uint32_t mFlags;
+ uint32_t mTrackId;
+ uint32_t mDefaultSampleDescriptionIndex;
+ uint32_t mDefaultSampleDuration;
+ uint32_t mDefaultSampleSize;
+ uint32_t mDefaultSampleFlags;
+};
+
+class Tfhd : public Trex
+{
+public:
+ explicit Tfhd(Trex& aTrex)
+ : Trex(aTrex)
+ , mBaseDataOffset(0)
+ {
+ mValid = aTrex.IsValid();
+ }
+ Tfhd(Box& aBox, Trex& aTrex);
+
+ uint64_t mBaseDataOffset;
+};
+
+class Tfdt : public Atom
+{
+public:
+ Tfdt()
+ : mBaseMediaDecodeTime(0)
+ {
+ }
+ explicit Tfdt(Box& aBox);
+
+ uint64_t mBaseMediaDecodeTime;
+};
+
+class Edts : public Atom
+{
+public:
+ Edts()
+ : mMediaStart(0)
+ , mEmptyOffset(0)
+ {
+ }
+ explicit Edts(Box& aBox);
+ virtual bool IsValid()
+ {
+ // edts is optional
+ return true;
+ }
+
+ int64_t mMediaStart;
+ int64_t mEmptyOffset;
+};
+
+struct Sample
+{
+ mozilla::MediaByteRange mByteRange;
+ mozilla::MediaByteRange mCencRange;
+ Microseconds mDecodeTime;
+ Interval<Microseconds> mCompositionRange;
+ bool mSync;
+};
+
+class Saiz final : public Atom
+{
+public:
+ Saiz(Box& aBox, AtomType aDefaultType);
+
+ AtomType mAuxInfoType;
+ uint32_t mAuxInfoTypeParameter;
+ FallibleTArray<uint8_t> mSampleInfoSize;
+};
+
+class Saio final : public Atom
+{
+public:
+ Saio(Box& aBox, AtomType aDefaultType);
+
+ AtomType mAuxInfoType;
+ uint32_t mAuxInfoTypeParameter;
+ FallibleTArray<uint64_t> mOffsets;
+};
+
+class AuxInfo {
+public:
+ AuxInfo(int64_t aMoofOffset, Saiz& aSaiz, Saio& aSaio);
+
+private:
+ int64_t mMoofOffset;
+ Saiz& mSaiz;
+ Saio& mSaio;
+};
+
+class Moof final : public Atom
+{
+public:
+ Moof(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, uint64_t* aDecoderTime, bool aIsAudio);
+ bool GetAuxInfo(AtomType aType, nsTArray<MediaByteRange>* aByteRanges);
+ void FixRounding(const Moof& aMoof);
+
+ mozilla::MediaByteRange mRange;
+ mozilla::MediaByteRange mMdatRange;
+ Interval<Microseconds> mTimeRange;
+ FallibleTArray<Sample> mIndex;
+
+ nsTArray<Saiz> mSaizs;
+ nsTArray<Saio> mSaios;
+
+private:
+ // aDecodeTime is updated to the end of the parsed TRAF on return.
+ void ParseTraf(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio);
+ // aDecodeTime is updated to the end of the parsed TRUN on return.
+ bool ParseTrun(Box& aBox, Tfhd& aTfhd, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, uint64_t* aDecodeTime, bool aIsAudio);
+ void ParseSaiz(Box& aBox);
+ void ParseSaio(Box& aBox);
+ bool ProcessCenc();
+ uint64_t mMaxRoundingError;
+};
+
+class MoofParser
+{
+public:
+ MoofParser(Stream* aSource, uint32_t aTrackId, bool aIsAudio)
+ : mSource(aSource)
+ , mOffset(0)
+ , mTrex(aTrackId)
+ , mIsAudio(aIsAudio)
+ , mLastDecodeTime(0)
+ {
+ // Setting the mTrex.mTrackId to 0 is a nasty work around for calculating
+ // the composition range for MSE. We need an array of tracks.
+ }
+ bool RebuildFragmentedIndex(
+ const mozilla::MediaByteRangeSet& aByteRanges);
+ // If *aCanEvict is set to true. then will remove all moofs already parsed
+ // from index then rebuild the index. *aCanEvict is set to true upon return if
+ // some moofs were removed.
+ bool RebuildFragmentedIndex(
+ const mozilla::MediaByteRangeSet& aByteRanges, bool* aCanEvict);
+ bool RebuildFragmentedIndex(BoxContext& aContext);
+ Interval<Microseconds> GetCompositionRange(
+ const mozilla::MediaByteRangeSet& aByteRanges);
+ bool ReachedEnd();
+ void ParseMoov(Box& aBox);
+ void ParseTrak(Box& aBox);
+ void ParseMdia(Box& aBox, Tkhd& aTkhd);
+ void ParseMvex(Box& aBox);
+
+ void ParseMinf(Box& aBox);
+ void ParseStbl(Box& aBox);
+ void ParseStsd(Box& aBox);
+ void ParseEncrypted(Box& aBox);
+ void ParseSinf(Box& aBox);
+
+ bool BlockingReadNextMoof();
+ bool HasMetadata();
+ already_AddRefed<mozilla::MediaByteBuffer> Metadata();
+ MediaByteRange FirstCompleteMediaSegment();
+ MediaByteRange FirstCompleteMediaHeader();
+
+ mozilla::MediaByteRange mInitRange;
+ RefPtr<Stream> mSource;
+ uint64_t mOffset;
+ Mvhd mMvhd;
+ Mdhd mMdhd;
+ Trex mTrex;
+ Tfdt mTfdt;
+ Edts mEdts;
+ Sinf mSinf;
+ nsTArray<Moof>& Moofs() { return mMoofs; }
+private:
+ void ScanForMetadata(mozilla::MediaByteRange& aFtyp,
+ mozilla::MediaByteRange& aMoov);
+ nsTArray<Moof> mMoofs;
+ nsTArray<MediaByteRange> mMediaRanges;
+ bool mIsAudio;
+ uint64_t mLastDecodeTime;
+};
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4_demuxer/ResourceStream.h b/media/libstagefright/binding/include/mp4_demuxer/ResourceStream.h
new file mode 100644
index 000000000..219465f17
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/ResourceStream.h
@@ -0,0 +1,49 @@
+/* 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/. */
+
+#ifndef RESOURCESTREAM_H_
+#define RESOURCESTREAM_H_
+
+#include "MediaResource.h"
+#include "mp4_demuxer/Stream.h"
+#include "mozilla/RefPtr.h"
+
+namespace mp4_demuxer
+{
+
+class ResourceStream : public Stream
+{
+public:
+ explicit ResourceStream(mozilla::MediaResource* aResource);
+
+ virtual bool ReadAt(int64_t offset, void* aBuffer, size_t aCount,
+ size_t* aBytesRead) override;
+ virtual bool CachedReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
+ size_t* aBytesRead) override;
+ virtual bool Length(int64_t* size) override;
+
+ void Pin()
+ {
+ mResource->Pin();
+ ++mPinCount;
+ }
+
+ void Unpin()
+ {
+ mResource->Unpin();
+ MOZ_ASSERT(mPinCount);
+ --mPinCount;
+ }
+
+protected:
+ virtual ~ResourceStream();
+
+private:
+ RefPtr<mozilla::MediaResource> mResource;
+ uint32_t mPinCount;
+};
+
+}
+
+#endif // RESOURCESTREAM_H_
diff --git a/media/libstagefright/binding/include/mp4_demuxer/SinfParser.h b/media/libstagefright/binding/include/mp4_demuxer/SinfParser.h
new file mode 100644
index 000000000..782a87907
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/SinfParser.h
@@ -0,0 +1,51 @@
+/* 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/. */
+
+
+#ifndef SINF_PARSER_H_
+#define SINF_PARSER_H_
+
+#include "mp4_demuxer/Atom.h"
+#include "mp4_demuxer/AtomType.h"
+
+namespace mp4_demuxer {
+
+class Box;
+
+class Sinf : public Atom
+{
+public:
+ Sinf()
+ : mDefaultIVSize(0)
+ , mDefaultEncryptionType()
+ {}
+ explicit Sinf(Box& aBox);
+
+ virtual bool IsValid() override
+ {
+ return !!mDefaultIVSize && !!mDefaultEncryptionType;
+ }
+
+ uint8_t mDefaultIVSize;
+ AtomType mDefaultEncryptionType;
+ uint8_t mDefaultKeyID[16];
+};
+
+class SinfParser
+{
+public:
+ explicit SinfParser(Box& aBox);
+
+ Sinf& GetSinf() { return mSinf; }
+private:
+ void ParseSchm(Box& aBox);
+ void ParseSchi(Box& aBox);
+ void ParseTenc(Box& aBox);
+
+ Sinf mSinf;
+};
+
+}
+
+#endif // SINF_PARSER_H_
diff --git a/media/libstagefright/binding/include/mp4_demuxer/Stream.h b/media/libstagefright/binding/include/mp4_demuxer/Stream.h
new file mode 100644
index 000000000..379478ec7
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4_demuxer/Stream.h
@@ -0,0 +1,32 @@
+/* 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/. */
+
+#ifndef STREAM_H_
+#define STREAM_H_
+
+#include "nsISupportsImpl.h"
+
+namespace mp4_demuxer
+{
+
+class Stream
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Stream);
+
+ virtual bool ReadAt(int64_t offset, void* data, size_t size,
+ size_t* bytes_read) = 0;
+ virtual bool CachedReadAt(int64_t offset, void* data, size_t size,
+ size_t* bytes_read) = 0;
+ virtual bool Length(int64_t* size) = 0;
+
+ virtual void DiscardBefore(int64_t offset) {}
+
+protected:
+ virtual ~Stream() {}
+};
+
+}
+
+#endif
diff --git a/media/libstagefright/binding/include/mp4parse.h b/media/libstagefright/binding/include/mp4parse.h
new file mode 100644
index 000000000..6650661cb
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -0,0 +1,113 @@
+
+#ifndef cheddar_generated_mp4parse_h
+#define cheddar_generated_mp4parse_h
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+
+// THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT
+
+// 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 https://mozilla.org/MPL/2.0/.
+
+typedef enum mp4parse_error {
+ MP4PARSE_OK = 0,
+ MP4PARSE_ERROR_BADARG = 1,
+ MP4PARSE_ERROR_INVALID = 2,
+ MP4PARSE_ERROR_UNSUPPORTED = 3,
+ MP4PARSE_ERROR_EOF = 4,
+ MP4PARSE_ERROR_IO = 5,
+} mp4parse_error;
+
+typedef enum mp4parse_track_type {
+ MP4PARSE_TRACK_TYPE_VIDEO = 0,
+ MP4PARSE_TRACK_TYPE_AUDIO = 1,
+} mp4parse_track_type;
+
+typedef enum mp4parse_codec {
+ MP4PARSE_CODEC_UNKNOWN,
+ MP4PARSE_CODEC_AAC,
+ MP4PARSE_CODEC_FLAC,
+ MP4PARSE_CODEC_OPUS,
+ MP4PARSE_CODEC_AVC,
+ MP4PARSE_CODEC_VP9,
+ MP4PARSE_CODEC_MP3,
+} mp4parse_codec;
+
+typedef struct mp4parse_track_info {
+ mp4parse_track_type track_type;
+ mp4parse_codec codec;
+ uint32_t track_id;
+ uint64_t duration;
+ int64_t media_time;
+} mp4parse_track_info;
+
+typedef struct mp4parse_codec_specific_config {
+ uint32_t length;
+ uint8_t const* data;
+} mp4parse_codec_specific_config;
+
+typedef struct mp4parse_track_audio_info {
+ uint16_t channels;
+ uint16_t bit_depth;
+ uint32_t sample_rate;
+ mp4parse_codec_specific_config codec_specific_config;
+} mp4parse_track_audio_info;
+
+typedef struct mp4parse_track_video_info {
+ uint32_t display_width;
+ uint32_t display_height;
+ uint16_t image_width;
+ uint16_t image_height;
+} mp4parse_track_video_info;
+
+typedef struct mp4parse_fragment_info {
+ uint64_t fragment_duration;
+} mp4parse_fragment_info;
+
+typedef struct mp4parse_parser mp4parse_parser;
+
+typedef struct mp4parse_io {
+ intptr_t (*read)(uint8_t* buffer, uintptr_t size, void* userdata);
+ void* userdata;
+} mp4parse_io;
+
+/// Allocate an `mp4parse_parser*` to read from the supplied `mp4parse_io`.
+mp4parse_parser* mp4parse_new(mp4parse_io const* io);
+
+/// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
+void mp4parse_free(mp4parse_parser* parser);
+
+/// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
+mp4parse_error mp4parse_read(mp4parse_parser* parser);
+
+/// Return the number of tracks parsed by previous `mp4parse_read()` call.
+mp4parse_error mp4parse_get_track_count(mp4parse_parser const* parser, uint32_t* count);
+
+/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
+mp4parse_error mp4parse_get_track_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_info* info);
+
+/// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
+mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_audio_info* info);
+
+/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
+mp4parse_error mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info);
+
+mp4parse_error mp4parse_get_fragment_info(mp4parse_parser* parser, mp4parse_fragment_info* info);
+
+mp4parse_error mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
diff --git a/media/libstagefright/binding/mp4parse-cargo.patch b/media/libstagefright/binding/mp4parse-cargo.patch
new file mode 100644
index 000000000..9b0ca7134
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse-cargo.patch
@@ -0,0 +1,45 @@
+diff --git a/media/libstagefright/binding/mp4parse/Cargo.toml b/media/libstagefright/binding/mp4parse/Cargo.toml
+index ff9422c..814c4c6 100644
+--- a/media/libstagefright/binding/mp4parse/Cargo.toml
++++ b/media/libstagefright/binding/mp4parse/Cargo.toml
+@@ -18,17 +18,11 @@ exclude = [
+ ]
+
+ [dependencies]
+-byteorder = "0.5.0"
+-afl = { version = "0.1.1", optional = true }
+-afl-plugin = { version = "0.1.1", optional = true }
+-abort_on_panic = { version = "1.0.0", optional = true }
++byteorder = "0.5.0"
+
+ [dev-dependencies]
+ test-assembler = "0.1.2"
+
+-[features]
+-fuzz = ["afl", "afl-plugin", "abort_on_panic"]
+-
+ # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
+ [profile.release]
+ debug-assertions = true
+diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+index aeeebc65..5c0836a 100644
+--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
++++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+@@ -18,17 +18,9 @@ exclude = [
+ "*.mp4",
+ ]
+
+-build = "build.rs"
+-
+ [dependencies]
+ "mp4parse" = {version = "0.6.0", path = "../mp4parse"}
+
+-[build-dependencies]
+-rusty-cheddar = "0.3.2"
+-
+-[features]
+-fuzz = ["mp4parse/fuzz"]
+-
+ # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
+ [profile.release]
+ debug-assertions = true
diff --git a/media/libstagefright/binding/mp4parse/Cargo.toml b/media/libstagefright/binding/mp4parse/Cargo.toml
new file mode 100644
index 000000000..affcef72b
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "mp4parse"
+version = "0.6.0"
+authors = [
+ "Ralph Giles <giles@mozilla.com>",
+ "Matthew Gregan <kinetik@flim.org>",
+ "Alfredo Yang <ayang@mozilla.com>",
+]
+
+description = "Parser for ISO base media file format (mp4)"
+documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
+license = "MPL-2.0"
+
+repository = "https://github.com/mozilla/mp4parse-rust"
+
+# Avoid complaints about trying to package test files.
+exclude = [
+ "*.mp4",
+]
+
+[dependencies]
+byteorder = "0.5.0"
+
+[dev-dependencies]
+test-assembler = "0.1.2"
+
+# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
+[profile.release]
+debug-assertions = true
diff --git a/media/libstagefright/binding/mp4parse/src/boxes.rs b/media/libstagefright/binding/mp4parse/src/boxes.rs
new file mode 100644
index 000000000..689439ade
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse/src/boxes.rs
@@ -0,0 +1,62 @@
+// 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 https://mozilla.org/MPL/2.0/.
+
+macro_rules! box_database {
+ ($($boxenum:ident $boxtype:expr),*,) => {
+ #[derive(Debug, Clone, Copy, PartialEq)]
+ pub enum BoxType {
+ $($boxenum),*,
+ UnknownBox(u32),
+ }
+
+ impl From<u32> for BoxType {
+ fn from(t: u32) -> BoxType {
+ use self::BoxType::*;
+ match t {
+ $($boxtype => $boxenum),*,
+ _ => UnknownBox(t),
+ }
+ }
+ }
+ }
+}
+
+box_database!(
+ FileTypeBox 0x66747970, // "ftyp"
+ MovieBox 0x6d6f6f76, // "moov"
+ MovieHeaderBox 0x6d766864, // "mvhd"
+ TrackBox 0x7472616b, // "trak"
+ TrackHeaderBox 0x746b6864, // "tkhd"
+ EditBox 0x65647473, // "edts"
+ MediaBox 0x6d646961, // "mdia"
+ EditListBox 0x656c7374, // "elst"
+ MediaHeaderBox 0x6d646864, // "mdhd"
+ HandlerBox 0x68646c72, // "hdlr"
+ MediaInformationBox 0x6d696e66, // "minf"
+ SampleTableBox 0x7374626c, // "stbl"
+ SampleDescriptionBox 0x73747364, // "stsd"
+ TimeToSampleBox 0x73747473, // "stts"
+ SampleToChunkBox 0x73747363, // "stsc"
+ SampleSizeBox 0x7374737a, // "stsz"
+ ChunkOffsetBox 0x7374636f, // "stco"
+ ChunkLargeOffsetBox 0x636f3634, // "co64"
+ SyncSampleBox 0x73747373, // "stss"
+ AVCSampleEntry 0x61766331, // "avc1"
+ AVC3SampleEntry 0x61766333, // "avc3" - Need to check official name in spec.
+ AVCConfigurationBox 0x61766343, // "avcC"
+ MP4AudioSampleEntry 0x6d703461, // "mp4a"
+ ESDBox 0x65736473, // "esds"
+ VP8SampleEntry 0x76703038, // "vp08"
+ VP9SampleEntry 0x76703039, // "vp09"
+ VPCodecConfigurationBox 0x76706343, // "vpcC"
+ FLACSampleEntry 0x664c6143, // "fLaC"
+ FLACSpecificBox 0x64664c61, // "dfLa"
+ OpusSampleEntry 0x4f707573, // "Opus"
+ OpusSpecificBox 0x644f7073, // "dOps"
+ ProtectedVisualSampleEntry 0x656e6376, // "encv" - Need to check official name in spec.
+ ProtectedAudioSampleEntry 0x656e6361, // "enca" - Need to check official name in spec.
+ MovieExtendsBox 0x6d766578, // "mvex"
+ MovieExtendsHeaderBox 0x6d656864, // "mehd"
+ QTWaveAtom 0x77617665, // "wave" - quicktime atom
+);
diff --git a/media/libstagefright/binding/mp4parse/src/lib.rs b/media/libstagefright/binding/mp4parse/src/lib.rs
new file mode 100644
index 000000000..37606a5e2
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -0,0 +1,1704 @@
+//! Module for parsing ISO Base Media Format aka video/mp4 streams.
+
+// 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 https://mozilla.org/MPL/2.0/.
+#![cfg_attr(feature = "fuzz", feature(plugin))]
+#![cfg_attr(feature = "fuzz", plugin(afl_plugin))]
+#[cfg(feature = "fuzz")]
+extern crate afl;
+
+extern crate byteorder;
+use byteorder::ReadBytesExt;
+use std::io::{Read, Take};
+use std::io::Cursor;
+use std::cmp;
+
+mod boxes;
+use boxes::BoxType;
+
+// Unit tests.
+#[cfg(test)]
+mod tests;
+
+// Arbitrary buffer size limit used for raw read_bufs on a box.
+const BUF_SIZE_LIMIT: u64 = 1024 * 1024;
+
+static DEBUG_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::ATOMIC_BOOL_INIT;
+
+pub fn set_debug_mode(mode: bool) {
+ DEBUG_MODE.store(mode, std::sync::atomic::Ordering::SeqCst);
+}
+
+#[inline(always)]
+fn get_debug_mode() -> bool {
+ DEBUG_MODE.load(std::sync::atomic::Ordering::Relaxed)
+}
+
+macro_rules! log {
+ ($($args:tt)*) => (
+ if get_debug_mode() {
+ println!( $( $args )* );
+ }
+ )
+}
+
+/// Describes parser failures.
+///
+/// This enum wraps the standard `io::Error` type, unified with
+/// our own parser error states and those of crates we use.
+#[derive(Debug)]
+pub enum Error {
+ /// Parse error caused by corrupt or malformed data.
+ InvalidData(&'static str),
+ /// Parse error caused by limited parser support rather than invalid data.
+ Unsupported(&'static str),
+ /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data.
+ UnexpectedEOF,
+ /// Propagate underlying errors from `std::io`.
+ Io(std::io::Error),
+ /// read_mp4 terminated without detecting a moov box.
+ NoMoov,
+}
+
+impl From<std::io::Error> for Error {
+ fn from(err: std::io::Error) -> Error {
+ match err.kind() {
+ std::io::ErrorKind::UnexpectedEof => Error::UnexpectedEOF,
+ _ => Error::Io(err),
+ }
+ }
+}
+
+impl From<std::string::FromUtf8Error> for Error {
+ fn from(_: std::string::FromUtf8Error) -> Error {
+ Error::InvalidData("invalid utf8")
+ }
+}
+
+/// Result shorthand using our Error enum.
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Basic ISO box structure.
+///
+/// mp4 files are a sequence of possibly-nested 'box' structures. Each box
+/// begins with a header describing the length of the box's data and a
+/// four-byte box type which identifies the type of the box. Together these
+/// are enough to interpret the contents of that section of the file.
+#[derive(Debug, Clone, Copy)]
+struct BoxHeader {
+ /// Box type.
+ name: BoxType,
+ /// Size of the box in bytes.
+ size: u64,
+ /// Offset to the start of the contained data (or header size).
+ offset: u64,
+}
+
+/// File type box 'ftyp'.
+#[derive(Debug)]
+struct FileTypeBox {
+ major_brand: u32,
+ minor_version: u32,
+ compatible_brands: Vec<u32>,
+}
+
+/// Movie header box 'mvhd'.
+#[derive(Debug)]
+struct MovieHeaderBox {
+ pub timescale: u32,
+ duration: u64,
+}
+
+/// Track header box 'tkhd'
+#[derive(Debug, Clone)]
+pub struct TrackHeaderBox {
+ track_id: u32,
+ pub disabled: bool,
+ pub duration: u64,
+ pub width: u32,
+ pub height: u32,
+}
+
+/// Edit list box 'elst'
+#[derive(Debug)]
+struct EditListBox {
+ edits: Vec<Edit>,
+}
+
+#[derive(Debug)]
+struct Edit {
+ segment_duration: u64,
+ media_time: i64,
+ media_rate_integer: i16,
+ media_rate_fraction: i16,
+}
+
+/// Media header box 'mdhd'
+#[derive(Debug)]
+struct MediaHeaderBox {
+ timescale: u32,
+ duration: u64,
+}
+
+// Chunk offset box 'stco' or 'co64'
+#[derive(Debug)]
+struct ChunkOffsetBox {
+ offsets: Vec<u64>,
+}
+
+// Sync sample box 'stss'
+#[derive(Debug)]
+struct SyncSampleBox {
+ samples: Vec<u32>,
+}
+
+// Sample to chunk box 'stsc'
+#[derive(Debug)]
+struct SampleToChunkBox {
+ samples: Vec<SampleToChunk>,
+}
+
+#[derive(Debug)]
+struct SampleToChunk {
+ first_chunk: u32,
+ samples_per_chunk: u32,
+ sample_description_index: u32,
+}
+
+// Sample size box 'stsz'
+#[derive(Debug)]
+struct SampleSizeBox {
+ sample_size: u32,
+ sample_sizes: Vec<u32>,
+}
+
+// Time to sample box 'stts'
+#[derive(Debug)]
+struct TimeToSampleBox {
+ samples: Vec<Sample>,
+}
+
+#[derive(Debug)]
+struct Sample {
+ sample_count: u32,
+ sample_delta: u32,
+}
+
+// Handler reference box 'hdlr'
+#[derive(Debug)]
+struct HandlerBox {
+ handler_type: u32,
+}
+
+// Sample description box 'stsd'
+#[derive(Debug)]
+struct SampleDescriptionBox {
+ descriptions: Vec<SampleEntry>,
+}
+
+#[derive(Debug, Clone)]
+pub enum SampleEntry {
+ Audio(AudioSampleEntry),
+ Video(VideoSampleEntry),
+ Unknown,
+}
+
+#[allow(non_camel_case_types)]
+#[derive(Debug, Clone)]
+pub struct ES_Descriptor {
+ pub audio_codec: CodecType,
+ pub audio_sample_rate: Option<u32>,
+ pub audio_channel_count: Option<u16>,
+ pub codec_specific_config: Vec<u8>,
+}
+
+#[allow(non_camel_case_types)]
+#[derive(Debug, Clone)]
+pub enum AudioCodecSpecific {
+ ES_Descriptor(ES_Descriptor),
+ FLACSpecificBox(FLACSpecificBox),
+ OpusSpecificBox(OpusSpecificBox),
+}
+
+#[derive(Debug, Clone)]
+pub struct AudioSampleEntry {
+ data_reference_index: u16,
+ pub channelcount: u16,
+ pub samplesize: u16,
+ pub samplerate: u32,
+ pub codec_specific: AudioCodecSpecific,
+}
+
+#[derive(Debug, Clone)]
+pub enum VideoCodecSpecific {
+ AVCConfig(Vec<u8>),
+ VPxConfig(VPxConfigBox),
+}
+
+#[derive(Debug, Clone)]
+pub struct VideoSampleEntry {
+ data_reference_index: u16,
+ pub width: u16,
+ pub height: u16,
+ pub codec_specific: VideoCodecSpecific,
+}
+
+/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9).
+#[derive(Debug, Clone)]
+pub struct VPxConfigBox {
+ profile: u8,
+ level: u8,
+ pub bit_depth: u8,
+ pub color_space: u8, // Really an enum
+ pub chroma_subsampling: u8,
+ transfer_function: u8,
+ video_full_range: bool,
+ pub codec_init: Vec<u8>, // Empty for vp8/vp9.
+}
+
+#[derive(Debug, Clone)]
+pub struct FLACMetadataBlock {
+ pub block_type: u8,
+ pub data: Vec<u8>,
+}
+
+/// Represet a FLACSpecificBox 'dfLa'
+#[derive(Debug, Clone)]
+pub struct FLACSpecificBox {
+ version: u8,
+ pub blocks: Vec<FLACMetadataBlock>,
+}
+
+#[derive(Debug, Clone)]
+struct ChannelMappingTable {
+ stream_count: u8,
+ coupled_count: u8,
+ channel_mapping: Vec<u8>,
+}
+
+/// Represent an OpusSpecificBox 'dOps'
+#[derive(Debug, Clone)]
+pub struct OpusSpecificBox {
+ pub version: u8,
+ output_channel_count: u8,
+ pre_skip: u16,
+ input_sample_rate: u32,
+ output_gain: i16,
+ channel_mapping_family: u8,
+ channel_mapping_table: Option<ChannelMappingTable>,
+}
+
+#[derive(Debug)]
+pub struct MovieExtendsBox {
+ pub fragment_duration: Option<MediaScaledTime>,
+}
+
+/// Internal data structures.
+#[derive(Debug, Default)]
+pub struct MediaContext {
+ pub timescale: Option<MediaTimeScale>,
+ /// Tracks found in the file.
+ pub tracks: Vec<Track>,
+ pub mvex: Option<MovieExtendsBox>,
+}
+
+impl MediaContext {
+ pub fn new() -> MediaContext {
+ Default::default()
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum TrackType {
+ Audio,
+ Video,
+ Unknown,
+}
+
+impl Default for TrackType {
+ fn default() -> Self { TrackType::Unknown }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum CodecType {
+ Unknown,
+ MP3,
+ AAC,
+ FLAC,
+ Opus,
+ H264,
+ VP9,
+ VP8,
+ EncryptedVideo,
+ EncryptedAudio,
+}
+
+impl Default for CodecType {
+ fn default() -> Self { CodecType::Unknown }
+}
+
+/// The media's global (mvhd) timescale in units per second.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct MediaTimeScale(pub u64);
+
+/// A time to be scaled by the media's global (mvhd) timescale.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct MediaScaledTime(pub u64);
+
+/// The track's local (mdhd) timescale.
+/// Members are timescale units per second and the track id.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct TrackTimeScale(pub u64, pub usize);
+
+/// A time to be scaled by the track's local (mdhd) timescale.
+/// Members are time in scale units and the track id.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct TrackScaledTime(pub u64, pub usize);
+
+/// A fragmented file contains no sample data in stts, stsc, and stco.
+#[derive(Debug, Default)]
+pub struct EmptySampleTableBoxes {
+ pub empty_stts : bool,
+ pub empty_stsc : bool,
+ pub empty_stco : bool,
+}
+
+/// Check boxes contain data.
+impl EmptySampleTableBoxes {
+ pub fn all_empty(&self) -> bool {
+ self.empty_stts & self.empty_stsc & self.empty_stco
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct Track {
+ id: usize,
+ pub track_type: TrackType,
+ pub empty_duration: Option<MediaScaledTime>,
+ pub media_time: Option<TrackScaledTime>,
+ pub timescale: Option<TrackTimeScale>,
+ pub duration: Option<TrackScaledTime>,
+ pub track_id: Option<u32>,
+ pub codec_type: CodecType,
+ pub empty_sample_boxes: EmptySampleTableBoxes,
+ pub data: Option<SampleEntry>,
+ pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
+}
+
+impl Track {
+ fn new(id: usize) -> Track {
+ Track { id: id, ..Default::default() }
+ }
+}
+
+struct BMFFBox<'a, T: 'a + Read> {
+ head: BoxHeader,
+ content: Take<&'a mut T>,
+}
+
+struct BoxIter<'a, T: 'a + Read> {
+ src: &'a mut T,
+}
+
+impl<'a, T: Read> BoxIter<'a, T> {
+ fn new(src: &mut T) -> BoxIter<T> {
+ BoxIter { src: src }
+ }
+
+ fn next_box(&mut self) -> Result<Option<BMFFBox<T>>> {
+ let r = read_box_header(self.src);
+ match r {
+ Ok(h) => Ok(Some(BMFFBox {
+ head: h,
+ content: self.src.take(h.size - h.offset),
+ })),
+ Err(Error::UnexpectedEOF) => Ok(None),
+ Err(e) => Err(e),
+ }
+ }
+}
+
+impl<'a, T: Read> Read for BMFFBox<'a, T> {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ self.content.read(buf)
+ }
+}
+
+impl<'a, T: Read> BMFFBox<'a, T> {
+ fn bytes_left(&self) -> usize {
+ self.content.limit() as usize
+ }
+
+ fn get_header(&self) -> &BoxHeader {
+ &self.head
+ }
+
+ fn box_iter<'b>(&'b mut self) -> BoxIter<BMFFBox<'a, T>> {
+ BoxIter::new(self)
+ }
+}
+
+/// Read and parse a box header.
+///
+/// Call this first to determine the type of a particular mp4 box
+/// and its length. Used internally for dispatching to specific
+/// parsers for the internal content, or to get the length to
+/// skip unknown or uninteresting boxes.
+fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
+ let size32 = try!(be_u32(src));
+ let name = BoxType::from(try!(be_u32(src)));
+ let size = match size32 {
+ // valid only for top-level box and indicates it's the last box in the file. usually mdat.
+ 0 => return Err(Error::Unsupported("unknown sized box")),
+ 1 => {
+ let size64 = try!(be_u64(src));
+ if size64 < 16 {
+ return Err(Error::InvalidData("malformed wide size"));
+ }
+ size64
+ }
+ 2...7 => return Err(Error::InvalidData("malformed size")),
+ _ => size32 as u64,
+ };
+ let offset = match size32 {
+ 1 => 4 + 4 + 8,
+ _ => 4 + 4,
+ };
+ assert!(offset <= size);
+ Ok(BoxHeader {
+ name: name,
+ size: size,
+ offset: offset,
+ })
+}
+
+/// Parse the extra header fields for a full box.
+fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
+ let version = try!(src.read_u8());
+ let flags_a = try!(src.read_u8());
+ let flags_b = try!(src.read_u8());
+ let flags_c = try!(src.read_u8());
+ Ok((version,
+ (flags_a as u32) << 16 | (flags_b as u32) << 8 | (flags_c as u32)))
+}
+
+/// Skip over the entire contents of a box.
+fn skip_box_content<T: Read>(src: &mut BMFFBox<T>) -> Result<()> {
+ // Skip the contents of unknown chunks.
+ let to_skip = {
+ let header = src.get_header();
+ log!("{:?} (skipped)", header);
+ (header.size - header.offset) as usize
+ };
+ assert!(to_skip == src.bytes_left());
+ skip(src, to_skip)
+}
+
+/// Skip over the remain data of a box.
+fn skip_box_remain<T: Read>(src: &mut BMFFBox<T>) -> Result<()> {
+ let remain = {
+ let header = src.get_header();
+ let len = src.bytes_left();
+ log!("remain {} (skipped) in {:?}", len, header);
+ len
+ };
+ skip(src, remain)
+}
+
+macro_rules! check_parser_state {
+ ( $src:expr ) => {
+ if $src.limit() > 0 {
+ log!("bad parser state: {} content bytes left", $src.limit());
+ return Err(Error::InvalidData("unread box content or bad parser sync"));
+ }
+ }
+}
+
+/// Read the contents of a box, including sub boxes.
+///
+/// Metadata is accumulated in the passed-through `MediaContext` struct,
+/// which can be examined later.
+pub fn read_mp4<T: Read>(f: &mut T, context: &mut MediaContext) -> Result<()> {
+ let mut found_ftyp = false;
+ let mut found_moov = false;
+ // TODO(kinetik): Top-level parsing should handle zero-sized boxes
+ // rather than throwing an error.
+ let mut iter = BoxIter::new(f);
+ while let Some(mut b) = try!(iter.next_box()) {
+ // box ordering: ftyp before any variable length box (inc. moov),
+ // but may not be first box in file if file signatures etc. present
+ // fragmented mp4 order: ftyp, moov, pairs of moof/mdat (1-multiple), mfra
+
+ // "special": uuid, wide (= 8 bytes)
+ // isom: moov, mdat, free, skip, udta, ftyp, moof, mfra
+ // iso2: pdin, meta
+ // iso3: meco
+ // iso5: styp, sidx, ssix, prft
+ // unknown, maybe: id32
+
+ // qt: pnot
+
+ // possibly allow anything where all printable and/or all lowercase printable
+ // "four printable characters from the ISO 8859-1 character set"
+ match b.head.name {
+ BoxType::FileTypeBox => {
+ let ftyp = try!(read_ftyp(&mut b));
+ found_ftyp = true;
+ log!("{:?}", ftyp);
+ }
+ BoxType::MovieBox => {
+ try!(read_moov(&mut b, context));
+ found_moov = true;
+ }
+ _ => try!(skip_box_content(&mut b)),
+ };
+ check_parser_state!(b.content);
+ if found_moov {
+ log!("found moov {}, could stop pure 'moov' parser now", if found_ftyp {
+ "and ftyp"
+ } else {
+ "but no ftyp"
+ });
+ }
+ }
+
+ // XXX(kinetik): This isn't perfect, as a "moov" with no contents is
+ // treated as okay but we haven't found anything useful. Needs more
+ // thought for clearer behaviour here.
+ if found_moov {
+ Ok(())
+ } else {
+ Err(Error::NoMoov)
+ }
+}
+
+fn parse_mvhd<T: Read>(f: &mut BMFFBox<T>) -> Result<(MovieHeaderBox, Option<MediaTimeScale>)> {
+ let mvhd = try!(read_mvhd(f));
+ if mvhd.timescale == 0 {
+ return Err(Error::InvalidData("zero timescale in mdhd"));
+ }
+ let timescale = Some(MediaTimeScale(mvhd.timescale as u64));
+ Ok((mvhd, timescale))
+}
+
+fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: &mut MediaContext) -> Result<()> {
+ let mut iter = f.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::MovieHeaderBox => {
+ let (mvhd, timescale) = try!(parse_mvhd(&mut b));
+ context.timescale = timescale;
+ log!("{:?}", mvhd);
+ }
+ BoxType::TrackBox => {
+ let mut track = Track::new(context.tracks.len());
+ try!(read_trak(&mut b, &mut track));
+ context.tracks.push(track);
+ }
+ BoxType::MovieExtendsBox => {
+ let mvex = try!(read_mvex(&mut b));
+ log!("{:?}", mvex);
+ context.mvex = Some(mvex);
+ }
+ _ => try!(skip_box_content(&mut b)),
+ };
+ check_parser_state!(b.content);
+ }
+ Ok(())
+}
+
+fn read_mvex<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieExtendsBox> {
+ let mut iter = src.box_iter();
+ let mut fragment_duration = None;
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::MovieExtendsHeaderBox => {
+ let duration = try!(read_mehd(&mut b));
+ fragment_duration = Some(duration);
+ },
+ _ => try!(skip_box_content(&mut b)),
+ }
+ }
+ Ok(MovieExtendsBox {
+ fragment_duration: fragment_duration,
+ })
+}
+
+fn read_mehd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaScaledTime> {
+ let (version, _) = try!(read_fullbox_extra(src));
+ let fragment_duration = match version {
+ 1 => try!(be_u64(src)),
+ 0 => try!(be_u32(src)) as u64,
+ _ => return Err(Error::InvalidData("unhandled mehd version")),
+ };
+ Ok(MediaScaledTime(fragment_duration))
+}
+
+fn read_trak<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+ let mut iter = f.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::TrackHeaderBox => {
+ let tkhd = try!(read_tkhd(&mut b));
+ track.track_id = Some(tkhd.track_id);
+ track.tkhd = Some(tkhd.clone());
+ log!("{:?}", tkhd);
+ }
+ BoxType::EditBox => try!(read_edts(&mut b, track)),
+ BoxType::MediaBox => try!(read_mdia(&mut b, track)),
+ _ => try!(skip_box_content(&mut b)),
+ };
+ check_parser_state!(b.content);
+ }
+ Ok(())
+}
+
+fn read_edts<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+ let mut iter = f.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::EditListBox => {
+ let elst = try!(read_elst(&mut b));
+ let mut empty_duration = 0;
+ let mut idx = 0;
+ if elst.edits.len() > 2 {
+ return Err(Error::Unsupported("more than two edits"));
+ }
+ if elst.edits[idx].media_time == -1 {
+ empty_duration = elst.edits[idx].segment_duration;
+ if elst.edits.len() < 2 {
+ return Err(Error::InvalidData("expected additional edit"));
+ }
+ idx += 1;
+ }
+ track.empty_duration = Some(MediaScaledTime(empty_duration));
+ if elst.edits[idx].media_time < 0 {
+ return Err(Error::InvalidData("unexpected negative media time in edit"));
+ }
+ track.media_time = Some(TrackScaledTime(elst.edits[idx].media_time as u64,
+ track.id));
+ log!("{:?}", elst);
+ }
+ _ => try!(skip_box_content(&mut b)),
+ };
+ check_parser_state!(b.content);
+ }
+ Ok(())
+}
+
+fn parse_mdhd<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<(MediaHeaderBox, Option<TrackScaledTime>, Option<TrackTimeScale>)> {
+ let mdhd = try!(read_mdhd(f));
+ let duration = match mdhd.duration {
+ std::u64::MAX => None,
+ duration => Some(TrackScaledTime(duration, track.id)),
+ };
+ if mdhd.timescale == 0 {
+ return Err(Error::InvalidData("zero timescale in mdhd"));
+ }
+ let timescale = Some(TrackTimeScale(mdhd.timescale as u64, track.id));
+ Ok((mdhd, duration, timescale))
+}
+
+fn read_mdia<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+ let mut iter = f.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::MediaHeaderBox => {
+ let (mdhd, duration, timescale) = try!(parse_mdhd(&mut b, track));
+ track.duration = duration;
+ track.timescale = timescale;
+ log!("{:?}", mdhd);
+ }
+ BoxType::HandlerBox => {
+ let hdlr = try!(read_hdlr(&mut b));
+ match hdlr.handler_type {
+ 0x76696465 /* 'vide' */ => track.track_type = TrackType::Video,
+ 0x736f756e /* 'soun' */ => track.track_type = TrackType::Audio,
+ _ => (),
+ }
+ log!("{:?}", hdlr);
+ }
+ BoxType::MediaInformationBox => try!(read_minf(&mut b, track)),
+ _ => try!(skip_box_content(&mut b)),
+ };
+ check_parser_state!(b.content);
+ }
+ Ok(())
+}
+
+fn read_minf<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+ let mut iter = f.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::SampleTableBox => try!(read_stbl(&mut b, track)),
+ _ => try!(skip_box_content(&mut b)),
+ };
+ check_parser_state!(b.content);
+ }
+ Ok(())
+}
+
+fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+ let mut iter = f.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::SampleDescriptionBox => {
+ let stsd = try!(read_stsd(&mut b, track));
+ log!("{:?}", stsd);
+ }
+ BoxType::TimeToSampleBox => {
+ let stts = try!(read_stts(&mut b));
+ track.empty_sample_boxes.empty_stts = stts.samples.is_empty();
+ log!("{:?}", stts);
+ }
+ BoxType::SampleToChunkBox => {
+ let stsc = try!(read_stsc(&mut b));
+ track.empty_sample_boxes.empty_stsc = stsc.samples.is_empty();
+ log!("{:?}", stsc);
+ }
+ BoxType::SampleSizeBox => {
+ let stsz = try!(read_stsz(&mut b));
+ log!("{:?}", stsz);
+ }
+ BoxType::ChunkOffsetBox => {
+ let stco = try!(read_stco(&mut b));
+ track.empty_sample_boxes.empty_stco = stco.offsets.is_empty();
+ log!("{:?}", stco);
+ }
+ BoxType::ChunkLargeOffsetBox => {
+ let co64 = try!(read_co64(&mut b));
+ log!("{:?}", co64);
+ }
+ BoxType::SyncSampleBox => {
+ let stss = try!(read_stss(&mut b));
+ log!("{:?}", stss);
+ }
+ _ => try!(skip_box_content(&mut b)),
+ };
+ check_parser_state!(b.content);
+ }
+ Ok(())
+}
+
+/// Parse an ftyp box.
+fn read_ftyp<T: Read>(src: &mut BMFFBox<T>) -> Result<FileTypeBox> {
+ let major = try!(be_u32(src));
+ let minor = try!(be_u32(src));
+ let bytes_left = src.bytes_left();
+ if bytes_left % 4 != 0 {
+ return Err(Error::InvalidData("invalid ftyp size"));
+ }
+ // Is a brand_count of zero valid?
+ let brand_count = bytes_left / 4;
+ let mut brands = Vec::new();
+ for _ in 0..brand_count {
+ brands.push(try!(be_u32(src)));
+ }
+ Ok(FileTypeBox {
+ major_brand: major,
+ minor_version: minor,
+ compatible_brands: brands,
+ })
+}
+
+/// Parse an mvhd box.
+fn read_mvhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieHeaderBox> {
+ let (version, _) = try!(read_fullbox_extra(src));
+ match version {
+ // 64 bit creation and modification times.
+ 1 => {
+ try!(skip(src, 16));
+ }
+ // 32 bit creation and modification times.
+ 0 => {
+ try!(skip(src, 8));
+ }
+ _ => return Err(Error::InvalidData("unhandled mvhd version")),
+ }
+ let timescale = try!(be_u32(src));
+ let duration = match version {
+ 1 => try!(be_u64(src)),
+ 0 => {
+ let d = try!(be_u32(src));
+ if d == std::u32::MAX {
+ std::u64::MAX
+ } else {
+ d as u64
+ }
+ }
+ _ => return Err(Error::InvalidData("unhandled mvhd version")),
+ };
+ // Skip remaining fields.
+ try!(skip(src, 80));
+ Ok(MovieHeaderBox {
+ timescale: timescale,
+ duration: duration,
+ })
+}
+
+/// Parse a tkhd box.
+fn read_tkhd<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackHeaderBox> {
+ let (version, flags) = try!(read_fullbox_extra(src));
+ let disabled = flags & 0x1u32 == 0 || flags & 0x2u32 == 0;
+ match version {
+ // 64 bit creation and modification times.
+ 1 => {
+ try!(skip(src, 16));
+ }
+ // 32 bit creation and modification times.
+ 0 => {
+ try!(skip(src, 8));
+ }
+ _ => return Err(Error::InvalidData("unhandled tkhd version")),
+ }
+ let track_id = try!(be_u32(src));
+ try!(skip(src, 4));
+ let duration = match version {
+ 1 => try!(be_u64(src)),
+ 0 => try!(be_u32(src)) as u64,
+ _ => return Err(Error::InvalidData("unhandled tkhd version")),
+ };
+ // Skip uninteresting fields.
+ try!(skip(src, 52));
+ let width = try!(be_u32(src));
+ let height = try!(be_u32(src));
+ Ok(TrackHeaderBox {
+ track_id: track_id,
+ disabled: disabled,
+ duration: duration,
+ width: width,
+ height: height,
+ })
+}
+
+/// Parse a elst box.
+fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> {
+ let (version, _) = try!(read_fullbox_extra(src));
+ let edit_count = try!(be_u32(src));
+ if edit_count == 0 {
+ return Err(Error::InvalidData("invalid edit count"));
+ }
+ let mut edits = Vec::new();
+ for _ in 0..edit_count {
+ let (segment_duration, media_time) = match version {
+ 1 => {
+ // 64 bit segment duration and media times.
+ (try!(be_u64(src)), try!(be_i64(src)))
+ }
+ 0 => {
+ // 32 bit segment duration and media times.
+ (try!(be_u32(src)) as u64, try!(be_i32(src)) as i64)
+ }
+ _ => return Err(Error::InvalidData("unhandled elst version")),
+ };
+ let media_rate_integer = try!(be_i16(src));
+ let media_rate_fraction = try!(be_i16(src));
+ edits.push(Edit {
+ segment_duration: segment_duration,
+ media_time: media_time,
+ media_rate_integer: media_rate_integer,
+ media_rate_fraction: media_rate_fraction,
+ })
+ }
+
+ Ok(EditListBox {
+ edits: edits,
+ })
+}
+
+/// Parse a mdhd box.
+fn read_mdhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaHeaderBox> {
+ let (version, _) = try!(read_fullbox_extra(src));
+ let (timescale, duration) = match version {
+ 1 => {
+ // Skip 64-bit creation and modification times.
+ try!(skip(src, 16));
+
+ // 64 bit duration.
+ (try!(be_u32(src)), try!(be_u64(src)))
+ }
+ 0 => {
+ // Skip 32-bit creation and modification times.
+ try!(skip(src, 8));
+
+ // 32 bit duration.
+ let timescale = try!(be_u32(src));
+ let duration = {
+ // Since we convert the 32-bit duration to 64-bit by
+ // upcasting, we need to preserve the special all-1s
+ // ("unknown") case by hand.
+ let d = try!(be_u32(src));
+ if d == std::u32::MAX {
+ std::u64::MAX
+ } else {
+ d as u64
+ }
+ };
+ (timescale, duration)
+ }
+ _ => return Err(Error::InvalidData("unhandled mdhd version")),
+ };
+
+ // Skip uninteresting fields.
+ try!(skip(src, 4));
+
+ Ok(MediaHeaderBox {
+ timescale: timescale,
+ duration: duration,
+ })
+}
+
+/// Parse a stco box.
+fn read_stco<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+ let offset_count = try!(be_u32(src));
+ let mut offsets = Vec::new();
+ for _ in 0..offset_count {
+ offsets.push(try!(be_u32(src)) as u64);
+ }
+
+ // Padding could be added in some contents.
+ try!(skip_box_remain(src));
+
+ Ok(ChunkOffsetBox {
+ offsets: offsets,
+ })
+}
+
+/// Parse a co64 box.
+fn read_co64<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+ let offset_count = try!(be_u32(src));
+ let mut offsets = Vec::new();
+ for _ in 0..offset_count {
+ offsets.push(try!(be_u64(src)));
+ }
+
+ // Padding could be added in some contents.
+ try!(skip_box_remain(src));
+
+ Ok(ChunkOffsetBox {
+ offsets: offsets,
+ })
+}
+
+/// Parse a stss box.
+fn read_stss<T: Read>(src: &mut BMFFBox<T>) -> Result<SyncSampleBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+ let sample_count = try!(be_u32(src));
+ let mut samples = Vec::new();
+ for _ in 0..sample_count {
+ samples.push(try!(be_u32(src)));
+ }
+
+ // Padding could be added in some contents.
+ try!(skip_box_remain(src));
+
+ Ok(SyncSampleBox {
+ samples: samples,
+ })
+}
+
+/// Parse a stsc box.
+fn read_stsc<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleToChunkBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+ let sample_count = try!(be_u32(src));
+ let mut samples = Vec::new();
+ for _ in 0..sample_count {
+ let first_chunk = try!(be_u32(src));
+ let samples_per_chunk = try!(be_u32(src));
+ let sample_description_index = try!(be_u32(src));
+ samples.push(SampleToChunk {
+ first_chunk: first_chunk,
+ samples_per_chunk: samples_per_chunk,
+ sample_description_index: sample_description_index,
+ });
+ }
+
+ // Padding could be added in some contents.
+ try!(skip_box_remain(src));
+
+ Ok(SampleToChunkBox {
+ samples: samples,
+ })
+}
+
+/// Parse a stsz box.
+fn read_stsz<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleSizeBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+ let sample_size = try!(be_u32(src));
+ let sample_count = try!(be_u32(src));
+ let mut sample_sizes = Vec::new();
+ if sample_size == 0 {
+ for _ in 0..sample_count {
+ sample_sizes.push(try!(be_u32(src)));
+ }
+ }
+
+ // Padding could be added in some contents.
+ try!(skip_box_remain(src));
+
+ Ok(SampleSizeBox {
+ sample_size: sample_size,
+ sample_sizes: sample_sizes,
+ })
+}
+
+/// Parse a stts box.
+fn read_stts<T: Read>(src: &mut BMFFBox<T>) -> Result<TimeToSampleBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+ let sample_count = try!(be_u32(src));
+ let mut samples = Vec::new();
+ for _ in 0..sample_count {
+ let sample_count = try!(be_u32(src));
+ let sample_delta = try!(be_u32(src));
+ samples.push(Sample {
+ sample_count: sample_count,
+ sample_delta: sample_delta,
+ });
+ }
+
+ // Padding could be added in some contents.
+ try!(skip_box_remain(src));
+
+ Ok(TimeToSampleBox {
+ samples: samples,
+ })
+}
+
+/// Parse a VPx Config Box.
+fn read_vpcc<T: Read>(src: &mut BMFFBox<T>) -> Result<VPxConfigBox> {
+ let (version, _) = try!(read_fullbox_extra(src));
+ if version != 0 {
+ return Err(Error::Unsupported("unknown vpcC version"));
+ }
+
+ let profile = try!(src.read_u8());
+ let level = try!(src.read_u8());
+ let (bit_depth, color_space) = {
+ let byte = try!(src.read_u8());
+ ((byte >> 4) & 0x0f, byte & 0x0f)
+ };
+ let (chroma_subsampling, transfer_function, video_full_range) = {
+ let byte = try!(src.read_u8());
+ ((byte >> 4) & 0x0f, (byte >> 1) & 0x07, (byte & 1) == 1)
+ };
+
+ let codec_init_size = try!(be_u16(src));
+ let codec_init = try!(read_buf(src, codec_init_size as usize));
+
+ // TODO(rillian): validate field value ranges.
+ Ok(VPxConfigBox {
+ profile: profile,
+ level: level,
+ bit_depth: bit_depth,
+ color_space: color_space,
+ chroma_subsampling: chroma_subsampling,
+ transfer_function: transfer_function,
+ video_full_range: video_full_range,
+ codec_init: codec_init,
+ })
+}
+
+fn read_flac_metadata<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACMetadataBlock> {
+ let temp = try!(src.read_u8());
+ let block_type = temp & 0x7f;
+ let length = try!(be_u24(src));
+ if length as usize > src.bytes_left() {
+ return Err(Error::InvalidData(
+ "FLACMetadataBlock larger than parent box"));
+ }
+ let data = try!(read_buf(src, length as usize));
+ Ok(FLACMetadataBlock {
+ block_type: block_type,
+ data: data,
+ })
+}
+
+fn read_esds<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
+ // Tags for elementary stream description
+ const ESDESCR_TAG: u8 = 0x03;
+ const DECODER_CONFIG_TAG: u8 = 0x04;
+ const DECODER_SPECIFIC_TAG: u8 = 0x05;
+
+ let frequency_table =
+ vec![(0x1, 96000), (0x1, 88200), (0x2, 64000), (0x3, 48000),
+ (0x4, 44100), (0x5, 32000), (0x6, 24000), (0x7, 22050),
+ (0x8, 16000), (0x9, 12000), (0xa, 11025), (0xb, 8000),
+ (0xc, 7350)];
+
+ let (_, _) = try!(read_fullbox_extra(src));
+
+ let esds_size = src.head.size - src.head.offset - 4;
+ if esds_size > BUF_SIZE_LIMIT {
+ return Err(Error::InvalidData("esds box exceeds BUF_SIZE_LIMIT"));
+ }
+ let esds_array = try!(read_buf(src, esds_size as usize));
+
+ // Parsing DecoderConfig descriptor to get the object_profile_indicator
+ // for correct codec type, audio sample rate and channel counts.
+ let (object_profile_indicator, sample_frequency, channels) = {
+ let mut object_profile: u8 = 0;
+ let mut sample_frequency = None;
+ let mut channels = None;
+
+ // clone a esds cursor for parsing.
+ let esds = &mut Cursor::new(&esds_array);
+ let next_tag = try!(esds.read_u8());
+
+ if next_tag != ESDESCR_TAG {
+ return Err(Error::Unsupported("fail to parse ES descriptor"));
+ }
+
+ let esds_extend = try!(esds.read_u8());
+ // extension tag start from 0x80.
+ let esds_end = if esds_extend >= 0x80 {
+ // skip remaining extension.
+ try!(skip(esds, 2));
+ esds.position() + try!(esds.read_u8()) as u64
+ } else {
+ esds.position() + esds_extend as u64
+ };
+ try!(skip(esds, 2));
+
+ let esds_flags = try!(esds.read_u8());
+
+ // Stream dependency flag, first bit from left most.
+ if esds_flags & 0x80 > 0 {
+ // Skip uninteresting fields.
+ try!(skip(esds, 2));
+ }
+
+ // Url flag, second bit from left most.
+ if esds_flags & 0x40 > 0 {
+ // Skip uninteresting fields.
+ let skip_es_len: usize = try!(esds.read_u8()) as usize + 2;
+ try!(skip(esds, skip_es_len));
+ }
+
+ // find DecoderConfig descriptor (tag = DECODER_CONFIG_TAG)
+ if esds_end > esds.position() {
+ let next_tag = try!(esds.read_u8());
+ if next_tag == DECODER_CONFIG_TAG {
+ let dcds_extend = try!(esds.read_u8());
+ // extension tag start from 0x80.
+ if dcds_extend >= 0x80 {
+ // skip remains extension and length.
+ try!(skip(esds, 3));
+ }
+
+ object_profile = try!(esds.read_u8());
+
+ // Skip uninteresting fields.
+ try!(skip(esds, 12));
+ }
+ }
+
+
+ // find DecoderSpecific descriptor (tag = DECODER_SPECIFIC_TAG)
+ if esds_end > esds.position() {
+ let next_tag = try!(esds.read_u8());
+ if next_tag == DECODER_SPECIFIC_TAG {
+ let dsds_extend = try!(esds.read_u8());
+ // extension tag start from 0x80.
+ if dsds_extend >= 0x80 {
+ // skip remains extension and length.
+ try!(skip(esds, 3));
+ }
+
+ let audio_specific_config = try!(be_u16(esds));
+
+ let sample_index = (audio_specific_config & 0x07FF) >> 7;
+
+ let channel_counts = (audio_specific_config & 0x007F) >> 3;
+
+ sample_frequency =
+ frequency_table.iter().find(|item| item.0 == sample_index).map(|x| x.1);
+
+ channels = Some(channel_counts);
+ }
+ }
+
+ (object_profile, sample_frequency, channels)
+ };
+
+ let codec = match object_profile_indicator {
+ 0x40 | 0x41 => CodecType::AAC,
+ 0x6B => CodecType::MP3,
+ _ => CodecType::Unknown,
+ };
+
+ if codec == CodecType::Unknown {
+ return Err(Error::Unsupported("unknown audio codec"));
+ }
+
+ Ok(ES_Descriptor {
+ audio_codec: codec,
+ audio_sample_rate: sample_frequency,
+ audio_channel_count: channels,
+ codec_specific_config: esds_array,
+ })
+}
+
+/// Parse `FLACSpecificBox`.
+fn read_dfla<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACSpecificBox> {
+ let (version, flags) = try!(read_fullbox_extra(src));
+ if version != 0 {
+ return Err(Error::Unsupported("unknown dfLa (FLAC) version"));
+ }
+ if flags != 0 {
+ return Err(Error::InvalidData("no-zero dfLa (FLAC) flags"));
+ }
+ let mut blocks = Vec::new();
+ while src.bytes_left() > 0 {
+ let block = try!(read_flac_metadata(src));
+ blocks.push(block);
+ }
+ // The box must have at least one meta block, and the first block
+ // must be the METADATA_BLOCK_STREAMINFO
+ if blocks.is_empty() {
+ return Err(Error::InvalidData("FLACSpecificBox missing metadata"));
+ } else if blocks[0].block_type != 0 {
+ println!("flac metadata block:\n {:?}", blocks[0]);
+ return Err(Error::InvalidData(
+ "FLACSpecificBox must have STREAMINFO metadata first"));
+ } else if blocks[0].data.len() != 34 {
+ return Err(Error::InvalidData(
+ "FLACSpecificBox STREAMINFO block is the wrong size"));
+ }
+ Ok(FLACSpecificBox {
+ version: version,
+ blocks: blocks,
+ })
+}
+
+/// Parse `OpusSpecificBox`.
+fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> {
+ let version = try!(src.read_u8());
+ if version != 0 {
+ return Err(Error::Unsupported("unknown dOps (Opus) version"));
+ }
+
+ let output_channel_count = try!(src.read_u8());
+ let pre_skip = try!(be_u16(src));
+ let input_sample_rate = try!(be_u32(src));
+ let output_gain = try!(be_i16(src));
+ let channel_mapping_family = try!(src.read_u8());
+
+ let channel_mapping_table = if channel_mapping_family == 0 {
+ None
+ } else {
+ let stream_count = try!(src.read_u8());
+ let coupled_count = try!(src.read_u8());
+ let channel_mapping = try!(read_buf(src, output_channel_count as usize));
+
+ Some(ChannelMappingTable {
+ stream_count: stream_count,
+ coupled_count: coupled_count,
+ channel_mapping: channel_mapping,
+ })
+ };
+
+ // TODO(kinetik): validate field value ranges.
+ Ok(OpusSpecificBox {
+ version: version,
+ output_channel_count: output_channel_count,
+ pre_skip: pre_skip,
+ input_sample_rate: input_sample_rate,
+ output_gain: output_gain,
+ channel_mapping_family: channel_mapping_family,
+ channel_mapping_table: channel_mapping_table,
+ })
+}
+
+/// Re-serialize the Opus codec-specific config data as an `OpusHead` packet.
+///
+/// Some decoders expect the initialization data in the format used by the
+/// Ogg and WebM encapsulations. To support this we prepend the `OpusHead`
+/// tag and byte-swap the data from big- to little-endian relative to the
+/// dOps box.
+pub fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>(opus: &OpusSpecificBox, dst: &mut W) -> Result<()> {
+ match dst.write(b"OpusHead") {
+ Err(e) => return Err(Error::from(e)),
+ Ok(bytes) => {
+ if bytes != 8 {
+ return Err(Error::InvalidData("Couldn't write OpusHead tag."));
+ }
+ }
+ }
+ // In mp4 encapsulation, the version field is 0, but in ogg
+ // it is 1. While decoders generally accept zero as well, write
+ // out the version of the header we're supporting rather than
+ // whatever we parsed out of mp4.
+ try!(dst.write_u8(1));
+ try!(dst.write_u8(opus.output_channel_count));
+ try!(dst.write_u16::<byteorder::LittleEndian>(opus.pre_skip));
+ try!(dst.write_u32::<byteorder::LittleEndian>(opus.input_sample_rate));
+ try!(dst.write_i16::<byteorder::LittleEndian>(opus.output_gain));
+ try!(dst.write_u8(opus.channel_mapping_family));
+ match opus.channel_mapping_table {
+ None => {}
+ Some(ref table) => {
+ try!(dst.write_u8(table.stream_count));
+ try!(dst.write_u8(table.coupled_count));
+ match dst.write(&table.channel_mapping) {
+ Err(e) => return Err(Error::from(e)),
+ Ok(bytes) => {
+ if bytes != table.channel_mapping.len() {
+ return Err(Error::InvalidData("Couldn't write channel mapping table data."));
+ }
+ }
+ }
+ }
+ };
+ Ok(())
+}
+
+/// Parse a hdlr box.
+fn read_hdlr<T: Read>(src: &mut BMFFBox<T>) -> Result<HandlerBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+
+ // Skip uninteresting fields.
+ try!(skip(src, 4));
+
+ let handler_type = try!(be_u32(src));
+
+ // Skip uninteresting fields.
+ try!(skip(src, 12));
+
+ let bytes_left = src.bytes_left();
+ let _name = try!(read_null_terminated_string(src, bytes_left));
+
+ Ok(HandlerBox {
+ handler_type: handler_type,
+ })
+}
+
+/// Parse an video description inside an stsd box.
+fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
+ let name = src.get_header().name;
+ track.codec_type = match name {
+ BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264,
+ BoxType::VP8SampleEntry => CodecType::VP8,
+ BoxType::VP9SampleEntry => CodecType::VP9,
+ BoxType::ProtectedVisualSampleEntry => CodecType::EncryptedVideo,
+ _ => CodecType::Unknown,
+ };
+
+ // Skip uninteresting fields.
+ try!(skip(src, 6));
+
+ let data_reference_index = try!(be_u16(src));
+
+ // Skip uninteresting fields.
+ try!(skip(src, 16));
+
+ let width = try!(be_u16(src));
+ let height = try!(be_u16(src));
+
+ // Skip uninteresting fields.
+ try!(skip(src, 14));
+
+ let _compressorname = try!(read_fixed_length_pascal_string(src, 32));
+
+ // Skip uninteresting fields.
+ try!(skip(src, 4));
+
+ // Skip clap/pasp/etc. for now.
+ let mut codec_specific = None;
+ let mut iter = src.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::AVCConfigurationBox => {
+ if (name != BoxType::AVCSampleEntry &&
+ name != BoxType::AVC3SampleEntry &&
+ name != BoxType::ProtectedVisualSampleEntry) ||
+ codec_specific.is_some() {
+ return Err(Error::InvalidData("malformed video sample entry"));
+ }
+ let avcc_size = b.head.size - b.head.offset;
+ if avcc_size > BUF_SIZE_LIMIT {
+ return Err(Error::InvalidData("avcC box exceeds BUF_SIZE_LIMIT"));
+ }
+ let avcc = try!(read_buf(&mut b.content, avcc_size as usize));
+ // TODO(kinetik): Parse avcC box? For now we just stash the data.
+ codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc));
+ }
+ BoxType::VPCodecConfigurationBox => { // vpcC
+ if (name != BoxType::VP8SampleEntry &&
+ name != BoxType::VP9SampleEntry) ||
+ codec_specific.is_some() {
+ return Err(Error::InvalidData("malformed video sample entry"));
+ }
+ let vpcc = try!(read_vpcc(&mut b));
+ codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc));
+ }
+ _ => try!(skip_box_content(&mut b)),
+ }
+ check_parser_state!(b.content);
+ }
+
+ codec_specific
+ .map(|codec_specific| SampleEntry::Video(VideoSampleEntry {
+ data_reference_index: data_reference_index,
+ width: width,
+ height: height,
+ codec_specific: codec_specific,
+ }))
+ .ok_or_else(|| Error::InvalidData("malformed video sample entry"))
+}
+
+fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
+ let mut codec_specific = None;
+ let mut iter = src.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::ESDBox => {
+ let esds = try!(read_esds(&mut b));
+ codec_specific = Some(esds);
+ },
+ _ => try!(skip_box_content(&mut b)),
+ }
+ }
+
+ codec_specific.ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
+}
+
+/// Parse an audio description inside an stsd box.
+fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
+ let name = src.get_header().name;
+ track.codec_type = match name {
+ // TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
+ BoxType::MP4AudioSampleEntry => CodecType::AAC,
+ BoxType::FLACSampleEntry => CodecType::FLAC,
+ BoxType::OpusSampleEntry => CodecType::Opus,
+ BoxType::ProtectedAudioSampleEntry => CodecType::EncryptedAudio,
+ _ => CodecType::Unknown,
+ };
+
+ // Skip uninteresting fields.
+ try!(skip(src, 6));
+
+ let data_reference_index = try!(be_u16(src));
+
+ // XXX(kinetik): This is "reserved" in BMFF, but some old QT MOV variant
+ // uses it, need to work out if we have to support it. Without checking
+ // here and reading extra fields after samplerate (or bailing with an
+ // error), the parser loses sync completely.
+ let version = try!(be_u16(src));
+
+ // Skip uninteresting fields.
+ try!(skip(src, 6));
+
+ let channelcount = try!(be_u16(src));
+ let samplesize = try!(be_u16(src));
+
+ // Skip uninteresting fields.
+ try!(skip(src, 4));
+
+ let samplerate = try!(be_u32(src));
+
+ match version {
+ 0 => (),
+ 1 => {
+ // Quicktime sound sample description version 1.
+ // Skip uninteresting fields.
+ try!(skip(src, 16));
+ },
+ _ => return Err(Error::Unsupported("unsupported non-isom audio sample entry")),
+ }
+
+ // Skip chan/etc. for now.
+ let mut codec_specific = None;
+ let mut iter = src.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ match b.head.name {
+ BoxType::ESDBox => {
+ if (name != BoxType::MP4AudioSampleEntry &&
+ name != BoxType::ProtectedAudioSampleEntry) ||
+ codec_specific.is_some() {
+ return Err(Error::InvalidData("malformed audio sample entry"));
+ }
+
+ let esds = try!(read_esds(&mut b));
+ track.codec_type = esds.audio_codec;
+ codec_specific = Some(AudioCodecSpecific::ES_Descriptor(esds));
+ }
+ BoxType::FLACSpecificBox => {
+ if name != BoxType::FLACSampleEntry ||
+ codec_specific.is_some() {
+ return Err(Error::InvalidData("malformed audio sample entry"));
+ }
+ let dfla = try!(read_dfla(&mut b));
+ track.codec_type = CodecType::FLAC;
+ codec_specific = Some(AudioCodecSpecific::FLACSpecificBox(dfla));
+ }
+ BoxType::OpusSpecificBox => {
+ if name != BoxType::OpusSampleEntry ||
+ codec_specific.is_some() {
+ return Err(Error::InvalidData("malformed audio sample entry"));
+ }
+ let dops = try!(read_dops(&mut b));
+ track.codec_type = CodecType::Opus;
+ codec_specific = Some(AudioCodecSpecific::OpusSpecificBox(dops));
+ }
+ BoxType::QTWaveAtom => {
+ let qt_esds = try!(read_qt_wave_atom(&mut b));
+ track.codec_type = qt_esds.audio_codec;
+ codec_specific = Some(AudioCodecSpecific::ES_Descriptor(qt_esds));
+ }
+ _ => try!(skip_box_content(&mut b)),
+ }
+ check_parser_state!(b.content);
+ }
+
+ codec_specific
+ .map(|codec_specific| SampleEntry::Audio(AudioSampleEntry {
+ data_reference_index: data_reference_index,
+ channelcount: channelcount,
+ samplesize: samplesize,
+ samplerate: samplerate,
+ codec_specific: codec_specific,
+ }))
+ .ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
+}
+
+/// Parse a stsd box.
+fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleDescriptionBox> {
+ let (_, _) = try!(read_fullbox_extra(src));
+
+ let description_count = try!(be_u32(src));
+ let mut descriptions = Vec::new();
+
+ {
+ // TODO(kinetik): check if/when more than one desc per track? do we need to support?
+ let mut iter = src.box_iter();
+ while let Some(mut b) = try!(iter.next_box()) {
+ let description = match track.track_type {
+ TrackType::Video => read_video_sample_entry(&mut b, track),
+ TrackType::Audio => read_audio_sample_entry(&mut b, track),
+ TrackType::Unknown => Err(Error::Unsupported("unknown track type")),
+ };
+ let description = match description {
+ Ok(desc) => desc,
+ Err(Error::Unsupported(_)) => {
+ // read_{audio,video}_desc may have returned Unsupported
+ // after partially reading the box content, so we can't
+ // simply use skip_box_content here.
+ let to_skip = b.bytes_left();
+ try!(skip(&mut b, to_skip));
+ SampleEntry::Unknown
+ }
+ Err(e) => return Err(e),
+ };
+ if track.data.is_none() {
+ track.data = Some(description.clone());
+ } else {
+ log!("** don't know how to handle multiple descriptions **");
+ }
+ descriptions.push(description);
+ check_parser_state!(b.content);
+ if descriptions.len() == description_count as usize {
+ break;
+ }
+ }
+ }
+
+ // Padding could be added in some contents.
+ try!(skip_box_remain(src));
+
+ Ok(SampleDescriptionBox {
+ descriptions: descriptions,
+ })
+}
+
+/// Skip a number of bytes that we don't care to parse.
+fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
+ const BUF_SIZE: usize = 64 * 1024;
+ let mut buf = vec![0; BUF_SIZE];
+ while bytes > 0 {
+ let buf_size = cmp::min(bytes, BUF_SIZE);
+ let len = try!(src.take(buf_size as u64).read(&mut buf));
+ if len == 0 {
+ return Err(Error::UnexpectedEOF);
+ }
+ bytes -= len;
+ }
+ Ok(())
+}
+
+/// Read size bytes into a Vector or return error.
+fn read_buf<T: ReadBytesExt>(src: &mut T, size: usize) -> Result<Vec<u8>> {
+ let mut buf = vec![0; size];
+ let r = try!(src.read(&mut buf));
+ if r != size {
+ return Err(Error::InvalidData("failed buffer read"));
+ }
+ Ok(buf)
+}
+
+// TODO(kinetik): Find a copy of ISO/IEC 14496-1 to confirm various string encodings.
+// XXX(kinetik): definition of "null-terminated" string is fuzzy, we have:
+// - zero or more byte strings, with a single null terminating the string.
+// - zero byte strings with no null terminator (i.e. zero space in the box for the string)
+// - length-prefixed strings with no null terminator (e.g. bear_rotate_0.mp4)
+fn read_null_terminated_string<T: ReadBytesExt>(src: &mut T, mut size: usize) -> Result<String> {
+ let mut buf = Vec::new();
+ while size > 0 {
+ let c = try!(src.read_u8());
+ if c == 0 {
+ break;
+ }
+ buf.push(c);
+ size -= 1;
+ }
+ String::from_utf8(buf).map_err(From::from)
+}
+
+#[allow(dead_code)]
+fn read_pascal_string<T: ReadBytesExt>(src: &mut T) -> Result<String> {
+ let len = try!(src.read_u8());
+ let buf = try!(read_buf(src, len as usize));
+ String::from_utf8(buf).map_err(From::from)
+}
+
+// Weird string encoding with a length prefix and a fixed sized buffer which
+// contains padding if the string doesn't fill the buffer.
+fn read_fixed_length_pascal_string<T: Read>(src: &mut T, size: usize) -> Result<String> {
+ assert!(size > 0);
+ let len = cmp::min(try!(src.read_u8()) as usize, size - 1);
+ let buf = try!(read_buf(src, len));
+ try!(skip(src, size - 1 - buf.len()));
+ String::from_utf8(buf).map_err(From::from)
+}
+
+fn be_i16<T: ReadBytesExt>(src: &mut T) -> Result<i16> {
+ src.read_i16::<byteorder::BigEndian>().map_err(From::from)
+}
+
+fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> {
+ src.read_i32::<byteorder::BigEndian>().map_err(From::from)
+}
+
+fn be_i64<T: ReadBytesExt>(src: &mut T) -> Result<i64> {
+ src.read_i64::<byteorder::BigEndian>().map_err(From::from)
+}
+
+fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
+ src.read_u16::<byteorder::BigEndian>().map_err(From::from)
+}
+
+fn be_u24<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
+ src.read_uint::<byteorder::BigEndian>(3)
+ .map(|v| v as u32)
+ .map_err(From::from)
+}
+
+fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
+ src.read_u32::<byteorder::BigEndian>().map_err(From::from)
+}
+
+fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
+ src.read_u64::<byteorder::BigEndian>().map_err(From::from)
+}
diff --git a/media/libstagefright/binding/mp4parse/src/tests.rs b/media/libstagefright/binding/mp4parse/src/tests.rs
new file mode 100644
index 000000000..7063b3790
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse/src/tests.rs
@@ -0,0 +1,860 @@
+//! Module for parsing ISO Base Media Format aka video/mp4 streams.
+//! Internal unit tests.
+
+// 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 https://mozilla.org/MPL/2.0/.
+
+use std::io::Cursor;
+use super::read_mp4;
+use super::MediaContext;
+use super::Error;
+extern crate test_assembler;
+use self::test_assembler::*;
+
+use boxes::BoxType;
+
+enum BoxSize {
+ Short(u32),
+ Long(u64),
+ UncheckedShort(u32),
+ UncheckedLong(u64),
+ Auto,
+}
+
+fn make_box<F>(size: BoxSize, name: &[u8; 4], func: F) -> Cursor<Vec<u8>>
+ where F: Fn(Section) -> Section
+{
+ let mut section = Section::new();
+ let box_size = Label::new();
+ section = match size {
+ BoxSize::Short(size) | BoxSize::UncheckedShort(size) => section.B32(size),
+ BoxSize::Long(_) | BoxSize::UncheckedLong(_) => section.B32(1),
+ BoxSize::Auto => section.B32(&box_size),
+ };
+ section = section.append_bytes(name);
+ section = match size {
+ // The spec allows the 32-bit size to be 0 to indicate unknown
+ // length streams. It's not clear if this is valid when using a
+ // 64-bit size, so prohibit it for now.
+ BoxSize::Long(size) => {
+ assert!(size > 0);
+ section.B64(size)
+ }
+ BoxSize::UncheckedLong(size) => section.B64(size),
+ _ => section,
+ };
+ section = func(section);
+ match size {
+ BoxSize::Short(size) => {
+ if size > 0 {
+ assert_eq!(size as u64, section.size())
+ }
+ }
+ BoxSize::Long(size) => assert_eq!(size, section.size()),
+ BoxSize::Auto => {
+ assert!(section.size() <= u32::max_value() as u64,
+ "Tried to use a long box with BoxSize::Auto");
+ box_size.set_const(section.size());
+ }
+ // Skip checking BoxSize::Unchecked* cases.
+ _ => (),
+ }
+ Cursor::new(section.get_contents().unwrap())
+}
+
+fn make_fullbox<F>(size: BoxSize, name: &[u8; 4], version: u8, func: F) -> Cursor<Vec<u8>>
+ where F: Fn(Section) -> Section
+{
+ make_box(size, name, |s| {
+ func(s.B8(version)
+ .B8(0)
+ .B8(0)
+ .B8(0))
+ })
+}
+
+#[test]
+fn read_box_header_short() {
+ let mut stream = make_box(BoxSize::Short(8), b"test", |s| s);
+ let header = super::read_box_header(&mut stream).unwrap();
+ assert_eq!(header.name, BoxType::UnknownBox(0x74657374)); // "test"
+ assert_eq!(header.size, 8);
+}
+
+#[test]
+fn read_box_header_long() {
+ let mut stream = make_box(BoxSize::Long(16), b"test", |s| s);
+ let header = super::read_box_header(&mut stream).unwrap();
+ assert_eq!(header.name, BoxType::UnknownBox(0x74657374)); // "test"
+ assert_eq!(header.size, 16);
+}
+
+#[test]
+fn read_box_header_short_unknown_size() {
+ let mut stream = make_box(BoxSize::Short(0), b"test", |s| s);
+ match super::read_box_header(&mut stream) {
+ Err(Error::Unsupported(s)) => assert_eq!(s, "unknown sized box"),
+ _ => panic!("unexpected result reading box with unknown size"),
+ };
+}
+
+#[test]
+fn read_box_header_short_invalid_size() {
+ let mut stream = make_box(BoxSize::UncheckedShort(2), b"test", |s| s);
+ match super::read_box_header(&mut stream) {
+ Err(Error::InvalidData(s)) => assert_eq!(s, "malformed size"),
+ _ => panic!("unexpected result reading box with invalid size"),
+ };
+}
+
+#[test]
+fn read_box_header_long_invalid_size() {
+ let mut stream = make_box(BoxSize::UncheckedLong(2), b"test", |s| s);
+ match super::read_box_header(&mut stream) {
+ Err(Error::InvalidData(s)) => assert_eq!(s, "malformed wide size"),
+ _ => panic!("unexpected result reading box with invalid size"),
+ };
+}
+
+#[test]
+fn read_ftyp() {
+ let mut stream = make_box(BoxSize::Short(24), b"ftyp", |s| {
+ s.append_bytes(b"mp42")
+ .B32(0) // minor version
+ .append_bytes(b"isom")
+ .append_bytes(b"mp42")
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::FileTypeBox);
+ assert_eq!(stream.head.size, 24);
+ let parsed = super::read_ftyp(&mut stream).unwrap();
+ assert_eq!(parsed.major_brand, 0x6d703432); // mp42
+ assert_eq!(parsed.minor_version, 0);
+ assert_eq!(parsed.compatible_brands.len(), 2);
+ assert_eq!(parsed.compatible_brands[0], 0x69736f6d); // isom
+ assert_eq!(parsed.compatible_brands[1], 0x6d703432); // mp42
+}
+
+#[test]
+fn read_truncated_ftyp() {
+ // We declare a 24 byte box, but only write 20 bytes.
+ let mut stream = make_box(BoxSize::UncheckedShort(24), b"ftyp", |s| {
+ s.append_bytes(b"mp42")
+ .B32(0) // minor version
+ .append_bytes(b"isom")
+ });
+ let mut context = MediaContext::new();
+ match read_mp4(&mut stream, &mut context) {
+ Err(Error::UnexpectedEOF) => (),
+ Ok(_) => assert!(false, "expected an error result"),
+ _ => assert!(false, "expected a different error result"),
+ }
+}
+
+#[test]
+fn read_ftyp_case() {
+ // Brands in BMFF are represented as a u32, so it would seem clear that
+ // 0x6d703432 ("mp42") is not equal to 0x4d503432 ("MP42"), but some
+ // demuxers treat these as case-insensitive strings, e.g. street.mp4's
+ // major brand is "MP42". I haven't seen case-insensitive
+ // compatible_brands (which we also test here), but it doesn't seem
+ // unlikely given the major_brand behaviour.
+ let mut stream = make_box(BoxSize::Auto, b"ftyp", |s| {
+ s.append_bytes(b"MP42")
+ .B32(0) // minor version
+ .append_bytes(b"ISOM")
+ .append_bytes(b"MP42")
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::FileTypeBox);
+ assert_eq!(stream.head.size, 24);
+ let parsed = super::read_ftyp(&mut stream).unwrap();
+ assert_eq!(parsed.major_brand, 0x4d503432);
+ assert_eq!(parsed.minor_version, 0);
+ assert_eq!(parsed.compatible_brands.len(), 2);
+ assert_eq!(parsed.compatible_brands[0], 0x49534f4d); // ISOM
+ assert_eq!(parsed.compatible_brands[1], 0x4d503432); // MP42
+}
+
+#[test]
+fn read_elst_v0() {
+ let mut stream = make_fullbox(BoxSize::Short(28), b"elst", 0, |s| {
+ s.B32(1) // list count
+ // first entry
+ .B32(1234) // duration
+ .B32(5678) // time
+ .B16(12) // rate integer
+ .B16(34) // rate fraction
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::EditListBox);
+ assert_eq!(stream.head.size, 28);
+ let parsed = super::read_elst(&mut stream).unwrap();
+ assert_eq!(parsed.edits.len(), 1);
+ assert_eq!(parsed.edits[0].segment_duration, 1234);
+ assert_eq!(parsed.edits[0].media_time, 5678);
+ assert_eq!(parsed.edits[0].media_rate_integer, 12);
+ assert_eq!(parsed.edits[0].media_rate_fraction, 34);
+}
+
+#[test]
+fn read_elst_v1() {
+ let mut stream = make_fullbox(BoxSize::Short(56), b"elst", 1, |s| {
+ s.B32(2) // list count
+ // first entry
+ .B64(1234) // duration
+ .B64(5678) // time
+ .B16(12) // rate integer
+ .B16(34) // rate fraction
+ // second entry
+ .B64(1234) // duration
+ .B64(5678) // time
+ .B16(12) // rate integer
+ .B16(34) // rate fraction
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::EditListBox);
+ assert_eq!(stream.head.size, 56);
+ let parsed = super::read_elst(&mut stream).unwrap();
+ assert_eq!(parsed.edits.len(), 2);
+ assert_eq!(parsed.edits[1].segment_duration, 1234);
+ assert_eq!(parsed.edits[1].media_time, 5678);
+ assert_eq!(parsed.edits[1].media_rate_integer, 12);
+ assert_eq!(parsed.edits[1].media_rate_fraction, 34);
+}
+
+#[test]
+fn read_mdhd_v0() {
+ let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| {
+ s.B32(0)
+ .B32(0)
+ .B32(1234) // timescale
+ .B32(5678) // duration
+ .B32(0)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+ assert_eq!(stream.head.size, 32);
+ let parsed = super::read_mdhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, 5678);
+}
+
+#[test]
+fn read_mdhd_v1() {
+ let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| {
+ s.B64(0)
+ .B64(0)
+ .B32(1234) // timescale
+ .B64(5678) // duration
+ .B32(0)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+ assert_eq!(stream.head.size, 44);
+ let parsed = super::read_mdhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, 5678);
+}
+
+#[test]
+fn read_mdhd_unknown_duration() {
+ let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| {
+ s.B32(0)
+ .B32(0)
+ .B32(1234) // timescale
+ .B32(::std::u32::MAX) // duration
+ .B32(0)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+ assert_eq!(stream.head.size, 32);
+ let parsed = super::read_mdhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, ::std::u64::MAX);
+}
+
+#[test]
+fn read_mdhd_invalid_timescale() {
+ let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| {
+ s.B64(0)
+ .B64(0)
+ .B32(0) // timescale
+ .B64(5678) // duration
+ .B32(0)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+ assert_eq!(stream.head.size, 44);
+ let r = super::parse_mdhd(&mut stream, &mut super::Track::new(0));
+ assert_eq!(r.is_err(), true);
+}
+
+#[test]
+fn read_mvhd_v0() {
+ let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
+ s.B32(0)
+ .B32(0)
+ .B32(1234)
+ .B32(5678)
+ .append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 108);
+ let parsed = super::read_mvhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, 5678);
+}
+
+#[test]
+fn read_mvhd_v1() {
+ let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
+ s.B64(0)
+ .B64(0)
+ .B32(1234)
+ .B64(5678)
+ .append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 120);
+ let parsed = super::read_mvhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, 5678);
+}
+
+#[test]
+fn read_mvhd_invalid_timescale() {
+ let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
+ s.B64(0)
+ .B64(0)
+ .B32(0)
+ .B64(5678)
+ .append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 120);
+ let r = super::parse_mvhd(&mut stream);
+ assert_eq!(r.is_err(), true);
+}
+
+#[test]
+fn read_mvhd_unknown_duration() {
+ let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
+ s.B32(0)
+ .B32(0)
+ .B32(1234)
+ .B32(::std::u32::MAX)
+ .append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 108);
+ let parsed = super::read_mvhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, ::std::u64::MAX);
+}
+
+#[test]
+fn read_vpcc() {
+ let data_length = 12u16;
+ let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 0, |s| {
+ s.B8(2)
+ .B8(0)
+ .B8(0x82)
+ .B8(0)
+ .B16(data_length)
+ .append_repeated(42, data_length as usize)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox);
+ let r = super::read_vpcc(&mut stream);
+ assert!(r.is_ok());
+}
+
+#[test]
+fn read_hdlr() {
+ let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| {
+ s.B32(0)
+ .append_bytes(b"vide")
+ .B32(0)
+ .B32(0)
+ .B32(0)
+ .append_bytes(b"VideoHandler")
+ .B8(0) // null-terminate string
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::HandlerBox);
+ assert_eq!(stream.head.size, 45);
+ let parsed = super::read_hdlr(&mut stream).unwrap();
+ assert_eq!(parsed.handler_type, 0x76696465); // vide
+}
+
+#[test]
+fn read_hdlr_short_name() {
+ let mut stream = make_fullbox(BoxSize::Short(33), b"hdlr", 0, |s| {
+ s.B32(0)
+ .append_bytes(b"vide")
+ .B32(0)
+ .B32(0)
+ .B32(0)
+ .B8(0) // null-terminate string
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::HandlerBox);
+ assert_eq!(stream.head.size, 33);
+ let parsed = super::read_hdlr(&mut stream).unwrap();
+ assert_eq!(parsed.handler_type, 0x76696465); // vide
+}
+
+#[test]
+fn read_hdlr_zero_length_name() {
+ let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| {
+ s.B32(0)
+ .append_bytes(b"vide")
+ .B32(0)
+ .B32(0)
+ .B32(0)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::HandlerBox);
+ assert_eq!(stream.head.size, 32);
+ let parsed = super::read_hdlr(&mut stream).unwrap();
+ assert_eq!(parsed.handler_type, 0x76696465); // vide
+}
+
+fn flac_streaminfo() -> Vec<u8> {
+ vec![
+ 0x10, 0x00, 0x10, 0x00, 0x00, 0x0a, 0x11, 0x00,
+ 0x38, 0x32, 0x0a, 0xc4, 0x42, 0xf0, 0x00, 0xc9,
+ 0xdf, 0xae, 0xb5, 0x66, 0xfc, 0x02, 0x15, 0xa3,
+ 0xb1, 0x54, 0x61, 0x47, 0x0f, 0xfb, 0x05, 0x00,
+ 0x33, 0xad,
+ ]
+}
+
+#[test]
+fn read_flac() {
+ let mut stream = make_box(BoxSize::Auto, b"fLaC", |s| {
+ s.append_repeated(0, 6) // reserved
+ .B16(1) // data reference index
+ .B32(0) // reserved
+ .B32(0) // reserved
+ .B16(2) // channel count
+ .B16(16) // bits per sample
+ .B16(0) // pre_defined
+ .B16(0) // reserved
+ .B32(44100 << 16) // Sample rate
+ .append_bytes(&make_dfla(FlacBlockType::StreamInfo, true,
+ &flac_streaminfo(), FlacBlockLength::Correct)
+ .into_inner())
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ let mut track = super::Track::new(0);
+ let r = super::read_audio_sample_entry(&mut stream, &mut track);
+ r.unwrap();
+}
+
+#[derive(Clone, Copy)]
+enum FlacBlockType {
+ StreamInfo = 0,
+ _Padding = 1,
+ _Application = 2,
+ _Seektable = 3,
+ _Comment = 4,
+ _Cuesheet = 5,
+ _Picture = 6,
+ _Reserved,
+ _Invalid = 127,
+}
+
+enum FlacBlockLength {
+ Correct,
+ Incorrect(usize),
+}
+
+fn make_dfla(block_type: FlacBlockType, last: bool, data: &Vec<u8>,
+ data_length: FlacBlockLength) -> Cursor<Vec<u8>> {
+ assert!(data.len() < 1<<24);
+ make_fullbox(BoxSize::Auto, b"dfLa", 0, |s| {
+ let flag = match last {
+ true => 1,
+ false => 0,
+ };
+ let size = match data_length {
+ FlacBlockLength::Correct => (data.len() as u32) & 0xffffff,
+ FlacBlockLength::Incorrect(size) => {
+ assert!(size < 1<<24);
+ (size as u32) & 0xffffff
+ }
+ };
+ let block_type = (block_type as u32) & 0x7f;
+ s.B32(flag << 31 | block_type << 24 | size)
+ .append_bytes(data)
+ })
+}
+
+#[test]
+fn read_dfla() {
+ let mut stream = make_dfla(FlacBlockType::StreamInfo, true,
+ &flac_streaminfo(), FlacBlockLength::Correct);
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::FLACSpecificBox);
+ let dfla = super::read_dfla(&mut stream).unwrap();
+ assert_eq!(dfla.version, 0);
+}
+
+#[test]
+fn long_flac_metadata() {
+ let streaminfo = flac_streaminfo();
+ let mut stream = make_dfla(FlacBlockType::StreamInfo, true,
+ &streaminfo,
+ FlacBlockLength::Incorrect(streaminfo.len() + 4));
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::FLACSpecificBox);
+ let r = super::read_dfla(&mut stream);
+ assert!(r.is_err());
+}
+
+#[test]
+fn read_opus() {
+ let mut stream = make_box(BoxSize::Auto, b"Opus", |s| {
+ s.append_repeated(0, 6)
+ .B16(1) // data reference index
+ .B32(0)
+ .B32(0)
+ .B16(2) // channel count
+ .B16(16) // bits per sample
+ .B16(0)
+ .B16(0)
+ .B32(48000 << 16) // Sample rate is always 48 kHz for Opus.
+ .append_bytes(&make_dops().into_inner())
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ let mut track = super::Track::new(0);
+ let r = super::read_audio_sample_entry(&mut stream, &mut track);
+ assert!(r.is_ok());
+}
+
+fn make_dops() -> Cursor<Vec<u8>> {
+ make_box(BoxSize::Auto, b"dOps", |s| {
+ s.B8(0) // version
+ .B8(2) // channel count
+ .B16(348) // pre-skip
+ .B32(44100) // original sample rate
+ .B16(0) // gain
+ .B8(0) // channel mapping
+ })
+}
+
+#[test]
+fn read_dops() {
+ let mut stream = make_dops();
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::OpusSpecificBox);
+ let r = super::read_dops(&mut stream);
+ assert!(r.is_ok());
+}
+
+#[test]
+fn serialize_opus_header() {
+ let opus = super::OpusSpecificBox {
+ version: 0,
+ output_channel_count: 1,
+ pre_skip: 342,
+ input_sample_rate: 24000,
+ output_gain: 0,
+ channel_mapping_family: 0,
+ channel_mapping_table: None,
+ };
+ let mut v = Vec::<u8>::new();
+ super::serialize_opus_header(&opus, &mut v).unwrap();
+ assert!(v.len() == 19);
+ assert!(v == vec![
+ 0x4f, 0x70, 0x75, 0x73, 0x48,0x65, 0x61, 0x64,
+ 0x01, 0x01, 0x56, 0x01,
+ 0xc0, 0x5d, 0x00, 0x00,
+ 0x00, 0x00, 0x00,
+ ]);
+ let opus = super::OpusSpecificBox {
+ version: 0,
+ output_channel_count: 6,
+ pre_skip: 152,
+ input_sample_rate: 48000,
+ output_gain: 0,
+ channel_mapping_family: 1,
+ channel_mapping_table: Some(super::ChannelMappingTable {
+ stream_count: 4,
+ coupled_count: 2,
+ channel_mapping: vec![0, 4, 1, 2, 3, 5],
+ }),
+ };
+ let mut v = Vec::<u8>::new();
+ super::serialize_opus_header(&opus, &mut v).unwrap();
+ assert!(v.len() == 27);
+ assert!(v == vec![
+ 0x4f, 0x70, 0x75, 0x73, 0x48,0x65, 0x61, 0x64,
+ 0x01, 0x06, 0x98, 0x00,
+ 0x80, 0xbb, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x04, 0x02,
+ 0x00, 0x04, 0x01, 0x02, 0x03, 0x05,
+ ]);
+}
+
+#[test]
+fn avcc_limit() {
+ let mut stream = make_box(BoxSize::Auto, b"avc1", |s| {
+ s.append_repeated(0, 6)
+ .B16(1)
+ .append_repeated(0, 16)
+ .B16(320)
+ .B16(240)
+ .append_repeated(0, 14)
+ .append_repeated(0, 32)
+ .append_repeated(0, 4)
+ .B32(0xffffffff)
+ .append_bytes(b"avcC")
+ .append_repeated(0, 100)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ let mut track = super::Track::new(0);
+ match super::read_video_sample_entry(&mut stream, &mut track) {
+ Err(Error::InvalidData(s)) => assert_eq!(s, "avcC box exceeds BUF_SIZE_LIMIT"),
+ Ok(_) => assert!(false, "expected an error result"),
+ _ => assert!(false, "expected a different error result"),
+ }
+}
+
+#[test]
+fn esds_limit() {
+ let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
+ s.append_repeated(0, 6)
+ .B16(1)
+ .B32(0)
+ .B32(0)
+ .B16(2)
+ .B16(16)
+ .B16(0)
+ .B16(0)
+ .B32(48000 << 16)
+ .B32(0xffffffff)
+ .append_bytes(b"esds")
+ .append_repeated(0, 100)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ let mut track = super::Track::new(0);
+ match super::read_audio_sample_entry(&mut stream, &mut track) {
+ Err(Error::InvalidData(s)) => assert_eq!(s, "esds box exceeds BUF_SIZE_LIMIT"),
+ Ok(_) => assert!(false, "expected an error result"),
+ _ => assert!(false, "expected a different error result"),
+ }
+}
+
+#[test]
+fn esds_limit_2() {
+ let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
+ s.append_repeated(0, 6)
+ .B16(1)
+ .B32(0)
+ .B32(0)
+ .B16(2)
+ .B16(16)
+ .B16(0)
+ .B16(0)
+ .B32(48000 << 16)
+ .B32(8)
+ .append_bytes(b"esds")
+ .append_repeated(0, 4)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ let mut track = super::Track::new(0);
+ match super::read_audio_sample_entry(&mut stream, &mut track) {
+ Err(Error::UnexpectedEOF) => (),
+ Ok(_) => assert!(false, "expected an error result"),
+ _ => assert!(false, "expected a different error result"),
+ }
+}
+
+#[test]
+fn read_elst_zero_entries() {
+ let mut stream = make_fullbox(BoxSize::Auto, b"elst", 0, |s| {
+ s.B32(0)
+ .B16(12)
+ .B16(34)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ match super::read_elst(&mut stream) {
+ Err(Error::InvalidData(s)) => assert_eq!(s, "invalid edit count"),
+ Ok(_) => assert!(false, "expected an error result"),
+ _ => assert!(false, "expected a different error result"),
+ }
+}
+
+fn make_elst() -> Cursor<Vec<u8>> {
+ make_fullbox(BoxSize::Auto, b"elst", 1, |s| {
+ s.B32(1)
+ // first entry
+ .B64(1234) // duration
+ .B64(0xffffffffffffffff) // time
+ .B16(12) // rate integer
+ .B16(34) // rate fraction
+ })
+}
+
+#[test]
+fn read_edts_bogus() {
+ // First edit list entry has a media_time of -1, so we expect a second
+ // edit list entry to be present to provide a valid media_time.
+ let mut stream = make_box(BoxSize::Auto, b"edts", |s| {
+ s.append_bytes(&make_elst().into_inner())
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ let mut track = super::Track::new(0);
+ match super::read_edts(&mut stream, &mut track) {
+ Err(Error::InvalidData(s)) => assert_eq!(s, "expected additional edit"),
+ Ok(_) => assert!(false, "expected an error result"),
+ _ => assert!(false, "expected a different error result"),
+ }
+}
+
+#[test]
+fn invalid_pascal_string() {
+ // String claims to be 32 bytes long (we provide 33 bytes to account for
+ // the 1 byte length prefix).
+ let pstr = "\x20xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ let mut stream = Cursor::new(pstr);
+ // Reader wants to limit the total read length to 32 bytes, so any
+ // returned string must be no longer than 31 bytes.
+ let s = super::read_fixed_length_pascal_string(&mut stream, 32).unwrap();
+ assert_eq!(s.len(), 31);
+}
+
+#[test]
+fn skip_padding_in_boxes() {
+ // Padding data could be added in the end of these boxes. Parser needs to skip
+ // them instead of returning error.
+ let box_names = vec![b"stts", b"stsc", b"stsz", b"stco", b"co64", b"stss"];
+
+ for name in box_names {
+ let mut stream = make_fullbox(BoxSize::Auto, name, 1, |s| {
+ s.append_repeated(0, 100) // add padding data
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ match name {
+ b"stts" => {
+ super::read_stts(&mut stream).expect("fail to skip padding: stts");
+ },
+ b"stsc" => {
+ super::read_stsc(&mut stream).expect("fail to skip padding: stsc");
+ },
+ b"stsz" => {
+ super::read_stsz(&mut stream).expect("fail to skip padding: stsz");
+ },
+ b"stco" => {
+ super::read_stco(&mut stream).expect("fail to skip padding: stco");
+ },
+ b"co64" => {
+ super::read_co64(&mut stream).expect("fail to skip padding: co64");
+ },
+ b"stss" => {
+ super::read_stss(&mut stream).expect("fail to skip padding: stss");
+ },
+ _ => (),
+ }
+ }
+}
+
+#[test]
+fn skip_padding_in_stsd() {
+ // Padding data could be added in the end of stsd boxes. Parser needs to skip
+ // them instead of returning error.
+ let avc = make_box(BoxSize::Auto, b"avc1", |s| {
+ s.append_repeated(0, 6)
+ .B16(1)
+ .append_repeated(0, 16)
+ .B16(320)
+ .B16(240)
+ .append_repeated(0, 14)
+ .append_repeated(0, 32)
+ .append_repeated(0, 4)
+ .B32(0xffffffff)
+ .append_bytes(b"avcC")
+ .append_repeated(0, 100)
+ }).into_inner();
+ let mut stream = make_fullbox(BoxSize::Auto, b"stsd", 0, |s| {
+ s.B32(1)
+ .append_bytes(avc.as_slice())
+ .append_repeated(0, 100) // add padding data
+ });
+
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ super::read_stsd(&mut stream, &mut super::Track::new(0))
+ .expect("fail to skip padding: stsd");
+}
+
+#[test]
+fn read_qt_wave_atom() {
+ let esds = make_fullbox(BoxSize::Auto, b"esds", 0, |s| {
+ s.B8(0x03) // elementary stream descriptor tag
+ .B8(0x0b) // esds length
+ .append_repeated(0, 2)
+ .B8(0x00) // flags
+ .B8(0x04) // decoder config descriptor tag
+ .B8(0x0d) // dcds length
+ .B8(0x6b) // mp3
+ .append_repeated(0, 12)
+ }).into_inner();
+ let wave = make_box(BoxSize::Auto, b"wave", |s| {
+ s.append_bytes(esds.as_slice())
+ }).into_inner();
+ let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
+ s.append_repeated(0, 6)
+ .B16(1) // data_reference_count
+ .B16(1) // verion: qt -> 1
+ .append_repeated(0, 6)
+ .B16(2)
+ .B16(16)
+ .append_repeated(0, 4)
+ .B32(48000 << 16)
+ .append_repeated(0, 16)
+ .append_bytes(wave.as_slice())
+ });
+
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ let mut track = super::Track::new(0);
+ super::read_audio_sample_entry(&mut stream, &mut track)
+ .expect("fail to read qt wave atom");
+ assert_eq!(track.codec_type, super::CodecType::MP3);
+}
diff --git a/media/libstagefright/binding/mp4parse/tests/afl.rs b/media/libstagefright/binding/mp4parse/tests/afl.rs
new file mode 100644
index 000000000..d4ffec0df
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse/tests/afl.rs
@@ -0,0 +1,53 @@
+/// Regression tests from American Fuzzy Lop test cases.
+
+// 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 https://mozilla.org/MPL/2.0/.
+
+/// These all caused panics at some point during development.
+
+extern crate mp4parse;
+
+use std::io::Cursor;
+
+/// https://github.com/mozilla/mp4parse-rust/issues/2
+///
+/// Test a box with 4-byte size, smaller than the smallest header.
+#[test]
+fn fuzz_2() {
+ let mut c = Cursor::new(b"\x00\x00\x00\x04\xa6\x00\x04\xa6".to_vec());
+ let mut context = mp4parse::MediaContext::new();
+ let _ = mp4parse::read_mp4(&mut c, &mut context);
+}
+
+/// https://github.com/mozilla/mp4parse-rust/issues/4
+///
+/// Test a large (64 bit) box header with zero declared size.
+#[test]
+fn fuzz_4() {
+ let mut c = Cursor::new(b"\x00\x00\x00\x01\x30\x30\x30\x30\x00\x00\x00\x00\x00\x00\x00\x00".to_vec());
+ let mut context = mp4parse::MediaContext::new();
+ let _ = mp4parse::read_mp4(&mut c, &mut context);
+}
+
+/// https://github.com/mozilla/mp4parse-rust/issues/5
+///
+/// Declares 202116104 compatible brands but does not supply them,
+/// verifying read is properly bounded at the end of the stream.
+#[test]
+fn fuzz_5() {
+ let mut c = Cursor::new(b"\x30\x30\x30\x30\x66\x74\x79\x70\x30\x30\x30\x30\x30\x30\x30\x30".to_vec());
+ let mut context = mp4parse::MediaContext::new();
+ let _ = mp4parse::read_mp4(&mut c, &mut context);
+}
+
+/// https://github.com/mozilla/mp4parse-rust/issues/6
+///
+/// Declares an ftyp box with a single invalid (short - 3 byte) compatible
+/// brand and excludes the extra 3 bytes from the stream.
+#[test]
+fn fuzz_6() {
+ let mut c = Cursor::new(b"\x00\x00\x00\x13\x66\x74\x79\x70\x30\x30\x30\x30\x30\x30\x30\x30".to_vec());
+ let mut context = mp4parse::MediaContext::new();
+ let _ = mp4parse::read_mp4(&mut c, &mut context);
+}
diff --git a/media/libstagefright/binding/mp4parse/tests/minimal.mp4 b/media/libstagefright/binding/mp4parse/tests/minimal.mp4
new file mode 100644
index 000000000..9fe1e6722
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse/tests/minimal.mp4
Binary files differ
diff --git a/media/libstagefright/binding/mp4parse/tests/public.rs b/media/libstagefright/binding/mp4parse/tests/public.rs
new file mode 100644
index 000000000..1d36f5f5a
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse/tests/public.rs
@@ -0,0 +1,97 @@
+/// Check if needed fields are still public.
+
+// 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 https://mozilla.org/MPL/2.0/.
+
+extern crate mp4parse as mp4;
+
+use std::io::{Cursor, Read};
+use std::fs::File;
+
+// Taken from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
+#[test]
+fn public_api() {
+ let mut fd = File::open("tests/minimal.mp4").expect("Unknown file");
+ let mut buf = Vec::new();
+ fd.read_to_end(&mut buf).expect("File error");
+
+ let mut c = Cursor::new(&buf);
+ let mut context = mp4::MediaContext::new();
+ mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
+ assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000)));
+ for track in context.tracks {
+ match track.data {
+ Some(mp4::SampleEntry::Video(v)) => {
+ // track part
+ assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0)));
+ assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
+ assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0)));
+ assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12800, 0)));
+ assert_eq!(v.width, 320);
+ assert_eq!(v.height, 240);
+
+ // track.tkhd part
+ let tkhd = track.tkhd.unwrap();
+ assert_eq!(tkhd.disabled, false);
+ assert_eq!(tkhd.duration, 40);
+ assert_eq!(tkhd.width, 20971520);
+ assert_eq!(tkhd.height, 15728640);
+
+ // track.data part
+ assert_eq!(match v.codec_specific {
+ mp4::VideoCodecSpecific::AVCConfig(v) => {
+ assert!(v.len() > 0);
+ "AVC"
+ }
+ mp4::VideoCodecSpecific::VPxConfig(vpx) => {
+ // We don't enter in here, we just check if fields are public.
+ assert!(vpx.bit_depth > 0);
+ assert!(vpx.color_space > 0);
+ assert!(vpx.chroma_subsampling > 0);
+ assert!(vpx.codec_init.len() > 0);
+ "VPx"
+ }
+ }, "AVC");
+ }
+ Some(mp4::SampleEntry::Audio(a)) => {
+ // track part
+ assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1)));
+ assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
+ assert_eq!(track.media_time, Some(mp4::TrackScaledTime(1024, 1)));
+ assert_eq!(track.timescale, Some(mp4::TrackTimeScale(48000, 1)));
+
+ // track.tkhd part
+ let tkhd = track.tkhd.unwrap();
+ assert_eq!(tkhd.disabled, false);
+ assert_eq!(tkhd.duration, 62);
+ assert_eq!(tkhd.width, 0);
+ assert_eq!(tkhd.height, 0);
+
+ // track.data part
+ assert_eq!(match a.codec_specific {
+ mp4::AudioCodecSpecific::ES_Descriptor(esds) => {
+ assert_eq!(esds.audio_codec, mp4::CodecType::AAC);
+ assert_eq!(esds.audio_sample_rate.unwrap(), 48000);
+ "ES"
+ }
+ mp4::AudioCodecSpecific::FLACSpecificBox(flac) => {
+ // STREAMINFO block must be present and first.
+ assert!(flac.blocks.len() > 0);
+ assert!(flac.blocks[0].block_type == 0);
+ assert!(flac.blocks[0].data.len() == 34);
+ "FLAC"
+ }
+ mp4::AudioCodecSpecific::OpusSpecificBox(opus) => {
+ // We don't enter in here, we just check if fields are public.
+ assert!(opus.version > 0);
+ "Opus"
+ }
+ }, "ES");
+ assert!(a.samplesize > 0);
+ assert!(a.samplerate > 0);
+ }
+ Some(mp4::SampleEntry::Unknown) | None => {}
+ }
+ }
+}
diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
new file mode 100644
index 000000000..5c0836abe
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "mp4parse_capi"
+version = "0.6.0"
+authors = [
+ "Ralph Giles <giles@mozilla.com>",
+ "Matthew Gregan <kinetik@flim.org>",
+ "Alfredo Yang <ayang@mozilla.com>",
+]
+
+description = "Parser for ISO base media file format (mp4)"
+documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
+license = "MPL-2.0"
+
+repository = "https://github.com/mozilla/mp4parse-rust"
+
+# Avoid complaints about trying to package test files.
+exclude = [
+ "*.mp4",
+]
+
+[dependencies]
+"mp4parse" = {version = "0.6.0", path = "../mp4parse"}
+
+# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
+[profile.release]
+debug-assertions = true
diff --git a/media/libstagefright/binding/mp4parse_capi/build.rs b/media/libstagefright/binding/mp4parse_capi/build.rs
new file mode 100644
index 000000000..29f2a85ce
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse_capi/build.rs
@@ -0,0 +1,12 @@
+extern crate cheddar;
+
+fn main() {
+ println!("cargo:rerun-if-changed=src/lib.rs");
+ // Generate mp4parse.h.
+ cheddar::Cheddar::new().expect("could not read manifest")
+ .insert_code("// THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT\n\n")
+ .insert_code("// This Source Code Form is subject to the terms of the Mozilla Public\n")
+ .insert_code("// License, v. 2.0. If a copy of the MPL was not distributed with this\n")
+ .insert_code("// file, You can obtain one at https://mozilla.org/MPL/2.0/.")
+ .run_build("include/mp4parse.h");
+}
diff --git a/media/libstagefright/binding/mp4parse_capi/src/lib.rs b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
new file mode 100644
index 000000000..f52d8b169
--- /dev/null
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -0,0 +1,870 @@
+//! C API for mp4parse module.
+//!
+//! Parses ISO Base Media Format aka video/mp4 streams.
+//!
+//! # Examples
+//!
+//! ```rust
+//! extern crate mp4parse_capi;
+//! use std::io::Read;
+//!
+//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+//! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+//! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+//! match input.read(&mut buf) {
+//! Ok(n) => n as isize,
+//! Err(_) => -1,
+//! }
+//! }
+//!
+//! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
+//! let io = mp4parse_capi::mp4parse_io {
+//! read: buf_read,
+//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void
+//! };
+//! unsafe {
+//! let parser = mp4parse_capi::mp4parse_new(&io);
+//! let rv = mp4parse_capi::mp4parse_read(parser);
+//! assert_eq!(rv, mp4parse_capi::mp4parse_error::MP4PARSE_OK);
+//! mp4parse_capi::mp4parse_free(parser);
+//! }
+//! ```
+
+// 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 https://mozilla.org/MPL/2.0/.
+
+extern crate mp4parse;
+
+use std::io::Read;
+use std::collections::HashMap;
+
+// Symbols we need from our rust api.
+use mp4parse::MediaContext;
+use mp4parse::TrackType;
+use mp4parse::read_mp4;
+use mp4parse::Error;
+use mp4parse::SampleEntry;
+use mp4parse::AudioCodecSpecific;
+use mp4parse::VideoCodecSpecific;
+use mp4parse::MediaTimeScale;
+use mp4parse::MediaScaledTime;
+use mp4parse::TrackTimeScale;
+use mp4parse::TrackScaledTime;
+use mp4parse::serialize_opus_header;
+use mp4parse::CodecType;
+
+// rusty-cheddar's C enum generation doesn't namespace enum members by
+// prefixing them, so we're forced to do it in our member names until
+// https://github.com/Sean1708/rusty-cheddar/pull/35 is fixed. Importing
+// the members into the module namespace avoids doubling up on the
+// namespacing on the Rust side.
+use mp4parse_error::*;
+use mp4parse_track_type::*;
+
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum mp4parse_error {
+ MP4PARSE_OK = 0,
+ MP4PARSE_ERROR_BADARG = 1,
+ MP4PARSE_ERROR_INVALID = 2,
+ MP4PARSE_ERROR_UNSUPPORTED = 3,
+ MP4PARSE_ERROR_EOF = 4,
+ MP4PARSE_ERROR_IO = 5,
+}
+
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum mp4parse_track_type {
+ MP4PARSE_TRACK_TYPE_VIDEO = 0,
+ MP4PARSE_TRACK_TYPE_AUDIO = 1,
+}
+
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum mp4parse_codec {
+ MP4PARSE_CODEC_UNKNOWN,
+ MP4PARSE_CODEC_AAC,
+ MP4PARSE_CODEC_FLAC,
+ MP4PARSE_CODEC_OPUS,
+ MP4PARSE_CODEC_AVC,
+ MP4PARSE_CODEC_VP9,
+ MP4PARSE_CODEC_MP3,
+}
+
+#[repr(C)]
+pub struct mp4parse_track_info {
+ pub track_type: mp4parse_track_type,
+ pub codec: mp4parse_codec,
+ pub track_id: u32,
+ pub duration: u64,
+ pub media_time: i64, // wants to be u64? understand how elst adjustment works
+ // TODO(kinetik): include crypto guff
+}
+
+#[repr(C)]
+pub struct mp4parse_codec_specific_config {
+ pub length: u32,
+ pub data: *const u8,
+}
+
+impl Default for mp4parse_codec_specific_config {
+ fn default() -> Self {
+ mp4parse_codec_specific_config {
+ length: 0,
+ data: std::ptr::null_mut(),
+ }
+ }
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct mp4parse_track_audio_info {
+ pub channels: u16,
+ pub bit_depth: u16,
+ pub sample_rate: u32,
+ // TODO(kinetik):
+ // int32_t profile;
+ // int32_t extended_profile; // check types
+ codec_specific_config: mp4parse_codec_specific_config,
+}
+
+#[repr(C)]
+pub struct mp4parse_track_video_info {
+ pub display_width: u32,
+ pub display_height: u32,
+ pub image_width: u16,
+ pub image_height: u16,
+ // TODO(kinetik):
+ // extra_data
+ // codec_specific_config
+}
+
+#[repr(C)]
+pub struct mp4parse_fragment_info {
+ pub fragment_duration: u64,
+ // TODO:
+ // info in trex box.
+}
+
+// Even though mp4parse_parser is opaque to C, rusty-cheddar won't let us
+// use more than one member, so we introduce *another* wrapper.
+struct Wrap {
+ context: MediaContext,
+ io: mp4parse_io,
+ poisoned: bool,
+ opus_header: HashMap<u32, Vec<u8>>,
+}
+
+#[repr(C)]
+#[allow(non_camel_case_types)]
+pub struct mp4parse_parser(Wrap);
+
+impl mp4parse_parser {
+ fn context(&self) -> &MediaContext {
+ &self.0.context
+ }
+
+ fn context_mut(&mut self) -> &mut MediaContext {
+ &mut self.0.context
+ }
+
+ fn io_mut(&mut self) -> &mut mp4parse_io {
+ &mut self.0.io
+ }
+
+ fn poisoned(&self) -> bool {
+ self.0.poisoned
+ }
+
+ fn set_poisoned(&mut self, poisoned: bool) {
+ self.0.poisoned = poisoned;
+ }
+
+ fn opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>> {
+ &mut self.0.opus_header
+ }
+}
+
+#[repr(C)]
+#[derive(Clone)]
+pub struct mp4parse_io {
+ pub read: extern fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
+ pub userdata: *mut std::os::raw::c_void,
+}
+
+impl Read for mp4parse_io {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ if buf.len() > isize::max_value() as usize {
+ return Err(std::io::Error::new(std::io::ErrorKind::Other, "buf length overflow in mp4parse_io Read impl"));
+ }
+ let rv = (self.read)(buf.as_mut_ptr(), buf.len(), self.userdata);
+ if rv >= 0 {
+ Ok(rv as usize)
+ } else {
+ Err(std::io::Error::new(std::io::ErrorKind::Other, "I/O error in mp4parse_io Read impl"))
+ }
+ }
+}
+
+// C API wrapper functions.
+
+/// Allocate an `mp4parse_parser*` to read from the supplied `mp4parse_io`.
+#[no_mangle]
+pub unsafe extern fn mp4parse_new(io: *const mp4parse_io) -> *mut mp4parse_parser {
+ if io.is_null() || (*io).userdata.is_null() {
+ return std::ptr::null_mut();
+ }
+ // is_null() isn't available on a fn type because it can't be null (in
+ // Rust) by definition. But since this value is coming from the C API,
+ // it *could* be null. Ideally, we'd wrap it in an Option to represent
+ // reality, but this causes rusty-cheddar to emit the wrong type
+ // (https://github.com/Sean1708/rusty-cheddar/issues/30).
+ if ((*io).read as *mut std::os::raw::c_void).is_null() {
+ return std::ptr::null_mut();
+ }
+ let parser = Box::new(mp4parse_parser(Wrap {
+ context: MediaContext::new(),
+ io: (*io).clone(),
+ poisoned: false,
+ opus_header: HashMap::new(),
+ }));
+ Box::into_raw(parser)
+}
+
+/// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
+#[no_mangle]
+pub unsafe extern fn mp4parse_free(parser: *mut mp4parse_parser) {
+ assert!(!parser.is_null());
+ let _ = Box::from_raw(parser);
+}
+
+/// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
+#[no_mangle]
+pub unsafe extern fn mp4parse_read(parser: *mut mp4parse_parser) -> mp4parse_error {
+ // Validate arguments from C.
+ if parser.is_null() || (*parser).poisoned() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let mut context = (*parser).context_mut();
+ let mut io = (*parser).io_mut();
+
+ let r = read_mp4(io, context);
+ match r {
+ Ok(_) => MP4PARSE_OK,
+ Err(Error::NoMoov) | Err(Error::InvalidData(_)) => {
+ // Block further calls. We've probable lost sync.
+ (*parser).set_poisoned(true);
+ MP4PARSE_ERROR_INVALID
+ }
+ Err(Error::Unsupported(_)) => MP4PARSE_ERROR_UNSUPPORTED,
+ Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF,
+ Err(Error::Io(_)) => {
+ // Block further calls after a read failure.
+ // Getting std::io::ErrorKind::UnexpectedEof is normal
+ // but our From trait implementation should have converted
+ // those to our Error::UnexpectedEOF variant.
+ (*parser).set_poisoned(true);
+ MP4PARSE_ERROR_IO
+ }
+ }
+}
+
+/// Return the number of tracks parsed by previous `mp4parse_read()` call.
+#[no_mangle]
+pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_error {
+ // Validate arguments from C.
+ if parser.is_null() || count.is_null() || (*parser).poisoned() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+ let context = (*parser).context();
+
+ // Make sure the track count fits in a u32.
+ if context.tracks.len() > u32::max_value() as usize {
+ return MP4PARSE_ERROR_INVALID;
+ }
+ *count = context.tracks.len() as u32;
+ MP4PARSE_OK
+}
+
+/// Calculate numerator * scale / denominator, if possible.
+///
+/// Applying the associativity of integer arithmetic, we divide first
+/// and add the remainder after multiplying each term separately
+/// to preserve precision while leaving more headroom. That is,
+/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d.
+///
+/// Return None on overflow or if the denominator is zero.
+fn rational_scale(numerator: u64, denominator: u64, scale: u64) -> Option<u64> {
+ if denominator == 0 {
+ return None;
+ }
+ let integer = numerator / denominator;
+ let remainder = numerator % denominator;
+ match integer.checked_mul(scale) {
+ Some(integer) => remainder.checked_mul(scale)
+ .and_then(|remainder| (remainder/denominator).checked_add(integer)),
+ None => None,
+ }
+}
+
+fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<u64> {
+ let microseconds_per_second = 1000000;
+ rational_scale(time.0, scale.0, microseconds_per_second)
+}
+
+fn track_time_to_us(time: TrackScaledTime, scale: TrackTimeScale) -> Option<u64> {
+ assert!(time.1 == scale.1);
+ let microseconds_per_second = 1000000;
+ rational_scale(time.0, scale.0, microseconds_per_second)
+}
+
+/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
+#[no_mangle]
+pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error {
+ if parser.is_null() || info.is_null() || (*parser).poisoned() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let context = (*parser).context_mut();
+ let track_index: usize = track_index as usize;
+ let info: &mut mp4parse_track_info = &mut *info;
+
+ if track_index >= context.tracks.len() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ info.track_type = match context.tracks[track_index].track_type {
+ TrackType::Video => MP4PARSE_TRACK_TYPE_VIDEO,
+ TrackType::Audio => MP4PARSE_TRACK_TYPE_AUDIO,
+ TrackType::Unknown => return MP4PARSE_ERROR_UNSUPPORTED,
+ };
+
+ info.codec = match context.tracks[track_index].data {
+ Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific {
+ AudioCodecSpecific::OpusSpecificBox(_) =>
+ mp4parse_codec::MP4PARSE_CODEC_OPUS,
+ AudioCodecSpecific::FLACSpecificBox(_) =>
+ mp4parse_codec::MP4PARSE_CODEC_FLAC,
+ AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
+ mp4parse_codec::MP4PARSE_CODEC_AAC,
+ AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
+ mp4parse_codec::MP4PARSE_CODEC_MP3,
+ AudioCodecSpecific::ES_Descriptor(_) =>
+ mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+ },
+ Some(SampleEntry::Video(ref video)) => match video.codec_specific {
+ VideoCodecSpecific::VPxConfig(_) =>
+ mp4parse_codec::MP4PARSE_CODEC_VP9,
+ VideoCodecSpecific::AVCConfig(_) =>
+ mp4parse_codec::MP4PARSE_CODEC_AVC,
+ },
+ _ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+ };
+
+ let track = &context.tracks[track_index];
+
+ if let (Some(track_timescale),
+ Some(context_timescale)) = (track.timescale,
+ context.timescale) {
+ let media_time =
+ match track.media_time.map_or(Some(0), |media_time| {
+ track_time_to_us(media_time, track_timescale) }) {
+ Some(time) => time as i64,
+ None => return MP4PARSE_ERROR_INVALID,
+ };
+ let empty_duration =
+ match track.empty_duration.map_or(Some(0), |empty_duration| {
+ media_time_to_us(empty_duration, context_timescale) }) {
+ Some(time) => time as i64,
+ None => return MP4PARSE_ERROR_INVALID,
+ };
+ info.media_time = media_time - empty_duration;
+
+ if let Some(track_duration) = track.duration {
+ match track_time_to_us(track_duration, track_timescale) {
+ Some(duration) => info.duration = duration,
+ None => return MP4PARSE_ERROR_INVALID,
+ }
+ } else {
+ // Duration unknown; stagefright returns 0 for this.
+ info.duration = 0
+ }
+ } else {
+ return MP4PARSE_ERROR_INVALID
+ }
+
+ info.track_id = match track.track_id {
+ Some(track_id) => track_id,
+ None => return MP4PARSE_ERROR_INVALID,
+ };
+
+ MP4PARSE_OK
+}
+
+/// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
+#[no_mangle]
+pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_audio_info) -> mp4parse_error {
+ if parser.is_null() || info.is_null() || (*parser).poisoned() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let context = (*parser).context_mut();
+
+ if track_index as usize >= context.tracks.len() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let track = &context.tracks[track_index as usize];
+
+ match track.track_type {
+ TrackType::Audio => {}
+ _ => return MP4PARSE_ERROR_INVALID,
+ };
+
+ let audio = match track.data {
+ Some(ref data) => data,
+ None => return MP4PARSE_ERROR_INVALID,
+ };
+
+ let audio = match *audio {
+ SampleEntry::Audio(ref x) => x,
+ _ => return MP4PARSE_ERROR_INVALID,
+ };
+
+ (*info).channels = audio.channelcount;
+ (*info).bit_depth = audio.samplesize;
+ (*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point
+
+ match audio.codec_specific {
+ AudioCodecSpecific::ES_Descriptor(ref v) => {
+ if v.codec_specific_config.len() > std::u32::MAX as usize {
+ return MP4PARSE_ERROR_INVALID;
+ }
+ (*info).codec_specific_config.length = v.codec_specific_config.len() as u32;
+ (*info).codec_specific_config.data = v.codec_specific_config.as_ptr();
+ if let Some(rate) = v.audio_sample_rate {
+ (*info).sample_rate = rate;
+ }
+ if let Some(channels) = v.audio_channel_count {
+ (*info).channels = channels;
+ }
+ }
+ AudioCodecSpecific::FLACSpecificBox(ref flac) => {
+ // Return the STREAMINFO metadata block in the codec_specific.
+ let streaminfo = &flac.blocks[0];
+ if streaminfo.block_type != 0 || streaminfo.data.len() != 34 {
+ return MP4PARSE_ERROR_INVALID;
+ }
+ (*info).codec_specific_config.length = streaminfo.data.len() as u32;
+ (*info).codec_specific_config.data = streaminfo.data.as_ptr();
+ }
+ AudioCodecSpecific::OpusSpecificBox(ref opus) => {
+ let mut v = Vec::new();
+ match serialize_opus_header(opus, &mut v) {
+ Err(_) => {
+ return MP4PARSE_ERROR_INVALID;
+ }
+ Ok(_) => {
+ let header = (*parser).opus_header_mut();
+ header.insert(track_index, v);
+ match header.get(&track_index) {
+ None => {}
+ Some(v) => {
+ if v.len() > std::u32::MAX as usize {
+ return MP4PARSE_ERROR_INVALID;
+ }
+ (*info).codec_specific_config.length = v.len() as u32;
+ (*info).codec_specific_config.data = v.as_ptr();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ MP4PARSE_OK
+}
+
+/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
+#[no_mangle]
+pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_video_info) -> mp4parse_error {
+ if parser.is_null() || info.is_null() || (*parser).poisoned() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let context = (*parser).context_mut();
+
+ if track_index as usize >= context.tracks.len() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let track = &context.tracks[track_index as usize];
+
+ match track.track_type {
+ TrackType::Video => {}
+ _ => return MP4PARSE_ERROR_INVALID,
+ };
+
+ let video = match track.data {
+ Some(ref data) => data,
+ None => return MP4PARSE_ERROR_INVALID,
+ };
+
+ let video = match *video {
+ SampleEntry::Video(ref x) => x,
+ _ => return MP4PARSE_ERROR_INVALID,
+ };
+
+ if let Some(ref tkhd) = track.tkhd {
+ (*info).display_width = tkhd.width >> 16; // 16.16 fixed point
+ (*info).display_height = tkhd.height >> 16; // 16.16 fixed point
+ } else {
+ return MP4PARSE_ERROR_INVALID;
+ }
+ (*info).image_width = video.width;
+ (*info).image_height = video.height;
+
+ MP4PARSE_OK
+}
+
+#[no_mangle]
+pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, info: *mut mp4parse_fragment_info) -> mp4parse_error {
+ if parser.is_null() || info.is_null() || (*parser).poisoned() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let context = (*parser).context();
+ let info: &mut mp4parse_fragment_info = &mut *info;
+
+ info.fragment_duration = 0;
+
+ let duration = match context.mvex {
+ Some(ref mvex) => mvex.fragment_duration,
+ None => return MP4PARSE_ERROR_INVALID,
+ };
+
+ if let (Some(time), Some(scale)) = (duration, context.timescale) {
+ info.fragment_duration = match media_time_to_us(time, scale) {
+ Some(time_us) => time_us as u64,
+ None => return MP4PARSE_ERROR_INVALID,
+ }
+ }
+
+ MP4PARSE_OK
+}
+
+// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
+#[no_mangle]
+pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_id: u32, fragmented: *mut u8) -> mp4parse_error {
+ if parser.is_null() || (*parser).poisoned() {
+ return MP4PARSE_ERROR_BADARG;
+ }
+
+ let context = (*parser).context_mut();
+ let tracks = &context.tracks;
+ (*fragmented) = false as u8;
+
+ if context.mvex.is_none() {
+ return MP4PARSE_OK;
+ }
+
+ // check sample tables.
+ let mut iter = tracks.iter();
+ match iter.find(|track| track.track_id == Some(track_id)) {
+ Some(track) if track.empty_sample_boxes.all_empty() => (*fragmented) = true as u8,
+ Some(_) => {},
+ None => return MP4PARSE_ERROR_BADARG,
+ }
+
+ MP4PARSE_OK
+}
+
+#[cfg(test)]
+extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
+ panic!("panic_read shouldn't be called in these tests");
+}
+
+#[cfg(test)]
+extern fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
+ -1
+}
+
+#[cfg(test)]
+extern fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+
+ let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn new_parser() {
+ let mut dummy_value: u32 = 42;
+ let io = mp4parse_io {
+ read: panic_read,
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ unsafe {
+ let parser = mp4parse_new(&io);
+ assert!(!parser.is_null());
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+#[should_panic(expected = "assertion failed")]
+fn free_null_parser() {
+ unsafe {
+ mp4parse_free(std::ptr::null_mut());
+ }
+}
+
+#[test]
+fn get_track_count_null_parser() {
+ unsafe {
+ let mut count: u32 = 0;
+ let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
+ assert!(rv == MP4PARSE_ERROR_BADARG);
+ let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
+ assert!(rv == MP4PARSE_ERROR_BADARG);
+ }
+}
+
+#[test]
+fn arg_validation() {
+ unsafe {
+ // Passing a null mp4parse_io is an error.
+ let parser = mp4parse_new(std::ptr::null());
+ assert!(parser.is_null());
+
+ let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
+
+ // Passing an mp4parse_io with null members is an error.
+ let io = mp4parse_io { read: std::mem::transmute(null_mut),
+ userdata: null_mut };
+ let parser = mp4parse_new(&io);
+ assert!(parser.is_null());
+
+ let io = mp4parse_io { read: panic_read,
+ userdata: null_mut };
+ let parser = mp4parse_new(&io);
+ assert!(parser.is_null());
+
+ let mut dummy_value = 42;
+ let io = mp4parse_io {
+ read: std::mem::transmute(null_mut),
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let parser = mp4parse_new(&io);
+ assert!(parser.is_null());
+
+ // Passing a null mp4parse_parser is an error.
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(std::ptr::null_mut()));
+
+ let mut dummy_info = mp4parse_track_info {
+ track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+ codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+ track_id: 0,
+ duration: 0,
+ media_time: 0,
+ };
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
+
+ let mut dummy_video = mp4parse_track_video_info {
+ display_width: 0,
+ display_height: 0,
+ image_width: 0,
+ image_height: 0,
+ };
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
+
+ let mut dummy_audio = Default::default();
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
+ }
+}
+
+#[test]
+fn arg_validation_with_parser() {
+ unsafe {
+ let mut dummy_value = 42;
+ let io = mp4parse_io {
+ read: error_read,
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let parser = mp4parse_new(&io);
+ assert!(!parser.is_null());
+
+ // Our mp4parse_io read should simply fail with an error.
+ assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
+
+ // The parser is now poisoned and unusable.
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(parser));
+
+ // Null info pointers are an error.
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, std::ptr::null_mut()));
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()));
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()));
+
+ let mut dummy_info = mp4parse_track_info {
+ track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+ codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+ track_id: 0,
+ duration: 0,
+ media_time: 0,
+ };
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, &mut dummy_info));
+
+ let mut dummy_video = mp4parse_track_video_info {
+ display_width: 0,
+ display_height: 0,
+ image_width: 0,
+ image_height: 0,
+ };
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
+
+ let mut dummy_audio = Default::default();
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn get_track_count_poisoned_parser() {
+ unsafe {
+ let mut dummy_value = 42;
+ let io = mp4parse_io {
+ read: error_read,
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let parser = mp4parse_new(&io);
+ assert!(!parser.is_null());
+
+ // Our mp4parse_io read should simply fail with an error.
+ assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
+
+ let mut count: u32 = 0;
+ let rv = mp4parse_get_track_count(parser, &mut count);
+ assert!(rv == MP4PARSE_ERROR_BADARG);
+ }
+}
+
+#[test]
+fn arg_validation_with_data() {
+ unsafe {
+ let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
+ let io = mp4parse_io { read: valid_read,
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
+ let parser = mp4parse_new(&io);
+ assert!(!parser.is_null());
+
+ assert_eq!(MP4PARSE_OK, mp4parse_read(parser));
+
+ let mut count: u32 = 0;
+ assert_eq!(MP4PARSE_OK, mp4parse_get_track_count(parser, &mut count));
+ assert_eq!(2, count);
+
+ let mut info = mp4parse_track_info {
+ track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+ codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+ track_id: 0,
+ duration: 0,
+ media_time: 0,
+ };
+ assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 0, &mut info));
+ assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
+ assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AVC);
+ assert_eq!(info.track_id, 1);
+ assert_eq!(info.duration, 40000);
+ assert_eq!(info.media_time, 0);
+
+ assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 1, &mut info));
+ assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_AUDIO);
+ assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AAC);
+ assert_eq!(info.track_id, 2);
+ assert_eq!(info.duration, 61333);
+ assert_eq!(info.media_time, 21333);
+
+ let mut video = mp4parse_track_video_info {
+ display_width: 0,
+ display_height: 0,
+ image_width: 0,
+ image_height: 0,
+ };
+ assert_eq!(MP4PARSE_OK, mp4parse_get_track_video_info(parser, 0, &mut video));
+ assert_eq!(video.display_width, 320);
+ assert_eq!(video.display_height, 240);
+ assert_eq!(video.image_width, 320);
+ assert_eq!(video.image_height, 240);
+
+ let mut audio = Default::default();
+ assert_eq!(MP4PARSE_OK, mp4parse_get_track_audio_info(parser, 1, &mut audio));
+ assert_eq!(audio.channels, 1);
+ assert_eq!(audio.bit_depth, 16);
+ assert_eq!(audio.sample_rate, 48000);
+
+ // Test with an invalid track number.
+ let mut info = mp4parse_track_info {
+ track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+ codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+ track_id: 0,
+ duration: 0,
+ media_time: 0,
+ };
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 3, &mut info));
+ assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
+ assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_UNKNOWN);
+ assert_eq!(info.track_id, 0);
+ assert_eq!(info.duration, 0);
+ assert_eq!(info.media_time, 0);
+
+ let mut video = mp4parse_track_video_info { display_width: 0,
+ display_height: 0,
+ image_width: 0,
+ image_height: 0 };
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 3, &mut video));
+ assert_eq!(video.display_width, 0);
+ assert_eq!(video.display_height, 0);
+ assert_eq!(video.image_width, 0);
+ assert_eq!(video.image_height, 0);
+
+ let mut audio = Default::default();
+ assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 3, &mut audio));
+ assert_eq!(audio.channels, 0);
+ assert_eq!(audio.bit_depth, 0);
+ assert_eq!(audio.sample_rate, 0);
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn rational_scale_overflow() {
+ assert_eq!(rational_scale(17, 3, 1000), Some(5666));
+ let large = 0x4000_0000_0000_0000;
+ assert_eq!(rational_scale(large, 2, 2), Some(large));
+ assert_eq!(rational_scale(large, 4, 4), Some(large));
+ assert_eq!(rational_scale(large, 2, 8), None);
+ assert_eq!(rational_scale(large, 8, 4), Some(large/2));
+ assert_eq!(rational_scale(large + 1, 4, 4), Some(large+1));
+ assert_eq!(rational_scale(large, 40, 1000), None);
+}
+
+#[test]
+fn media_time_overflow() {
+ let scale = MediaTimeScale(90000);
+ let duration = MediaScaledTime(9007199254710000);
+ assert_eq!(media_time_to_us(duration, scale), Some(100079991719000000));
+}
+
+#[test]
+fn track_time_overflow() {
+ let scale = TrackTimeScale(44100, 0);
+ let duration = TrackScaledTime(4413527634807900, 0);
+ assert_eq!(track_time_to_us(duration, scale), Some(100079991719000000));
+}
diff --git a/media/libstagefright/binding/update-rust.sh b/media/libstagefright/binding/update-rust.sh
new file mode 100755
index 000000000..a8a462f6d
--- /dev/null
+++ b/media/libstagefright/binding/update-rust.sh
@@ -0,0 +1,56 @@
+#!/bin/sh -e
+# Script to update mp4parse-rust sources to latest upstream
+
+# Default version.
+VER=v0.6.0
+
+# Accept version or commit from the command line.
+if test -n "$1"; then
+ VER=$1
+fi
+
+echo "Fetching sources..."
+rm -rf _upstream
+git clone https://github.com/mozilla/mp4parse-rust _upstream/mp4parse
+pushd _upstream/mp4parse
+git checkout ${VER}
+echo "Verifying sources..."
+pushd mp4parse
+cargo test
+popd
+echo "Constructing C api header..."
+pushd mp4parse_capi
+cargo build
+echo "Verifying sources..."
+cargo test
+popd
+popd
+rm -rf mp4parse
+mkdir -p mp4parse/src
+cp _upstream/mp4parse/mp4parse/Cargo.toml mp4parse/
+cp _upstream/mp4parse/mp4parse/src/*.rs mp4parse/src/
+mkdir -p mp4parse/tests
+cp _upstream/mp4parse/mp4parse/tests/*.rs mp4parse/tests/
+cp _upstream/mp4parse/mp4parse/tests/*.mp4 mp4parse/tests/
+rm -rf mp4parse_capi
+mkdir -p mp4parse_capi/src
+cp _upstream/mp4parse/mp4parse_capi/Cargo.toml mp4parse_capi/
+cp _upstream/mp4parse/mp4parse_capi/build.rs mp4parse_capi/
+cp _upstream/mp4parse/mp4parse_capi/include/mp4parse.h include/
+cp _upstream/mp4parse/mp4parse_capi/src/*.rs mp4parse_capi/src/
+
+echo "Applying patches..."
+patch -p4 < mp4parse-cargo.patch
+
+echo "Cleaning up..."
+rm -rf _upstream
+
+echo "Updating gecko Cargo.lock..."
+pushd ../../../toolkit/library/rust/
+cargo update --package mp4parse_capi
+popd
+pushd ../../../toolkit/library/gtest/rust/
+cargo update --package mp4parse_capi
+popd
+
+echo "Updated to ${VER}."