diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /media/libstagefright/binding | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'media/libstagefright/binding')
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 Binary files differnew file mode 100644 index 000000000..9fe1e6722 --- /dev/null +++ b/media/libstagefright/binding/mp4parse/tests/minimal.mp4 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}." |