summaryrefslogtreecommitdiffstats
path: root/dom/media/mp4/Box.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/mp4/Box.cpp')
-rw-r--r--dom/media/mp4/Box.cpp230
1 files changed, 230 insertions, 0 deletions
diff --git a/dom/media/mp4/Box.cpp b/dom/media/mp4/Box.cpp
new file mode 100644
index 0000000000..334ba3e3f8
--- /dev/null
+++ b/dom/media/mp4/Box.cpp
@@ -0,0 +1,230 @@
+/* -*- 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 "Box.h"
+#include "ByteStream.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Unused.h"
+#include <algorithm>
+
+namespace mozilla {
+
+// 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), mBodyOffset(0), mChildOffset(0), mParent(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::ReadCompleteBox() const {
+ const size_t length = mRange.mEnd - mRange.mStart;
+ nsTArray<uint8_t> out(length);
+ out.SetLength(length);
+ size_t bytesRead = 0;
+ if (!mContext->mSource->CachedReadAt(mRange.mStart, out.Elements(), length,
+ &bytesRead) ||
+ bytesRead != length) {
+ // Byte ranges are being reported incorrectly
+ NS_WARNING("Read failed in mozilla::Box::ReadCompleteBox()");
+ return nsTArray<uint8_t>(0);
+ }
+ return out;
+}
+
+nsTArray<uint8_t> Box::Read() const {
+ nsTArray<uint8_t> out;
+ Unused << Read(&out, mRange);
+ return out;
+}
+
+bool Box::Read(nsTArray<uint8_t>* aDest, const MediaByteRange& aRange) const {
+ 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 mozilla::Box::Read()");
+ aDest->Clear();
+ return false;
+ }
+ return true;
+}
+
+ByteSlice Box::ReadAsSlice() {
+ if (!mContext || mRange.IsEmpty()) {
+ return ByteSlice{nullptr, 0};
+ }
+
+ 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(mRange.mEnd - mChildOffset, kMAX_BOX_READ);
+ } else {
+ length = mRange.mEnd - mChildOffset;
+ }
+
+ const uint8_t* data =
+ mContext->mSource->GetContiguousAccess(mChildOffset, length);
+ if (data) {
+ // We can direct access the underlying storage of the ByteStream.
+ return ByteSlice{data, size_t(length)};
+ }
+
+ uint8_t* p = mContext->mAllocator.Allocate(size_t(length));
+ size_t bytes;
+ if (!mContext->mSource->CachedReadAt(mChildOffset, p, length, &bytes) ||
+ bytes != length) {
+ // Byte ranges are being reported incorrectly
+ NS_WARNING("Read failed in mozilla::Box::ReadAsSlice()");
+ return ByteSlice{nullptr, 0};
+ }
+ return ByteSlice{p, size_t(length)};
+}
+
+const size_t BLOCK_CAPACITY = 16 * 1024;
+
+uint8_t* BumpAllocator::Allocate(size_t aNumBytes) {
+ if (aNumBytes > BLOCK_CAPACITY) {
+ mBuffers.AppendElement(nsTArray<uint8_t>(aNumBytes));
+ mBuffers.LastElement().SetLength(aNumBytes);
+ return mBuffers.LastElement().Elements();
+ }
+ for (nsTArray<uint8_t>& buffer : mBuffers) {
+ if (buffer.Length() + aNumBytes < BLOCK_CAPACITY) {
+ size_t offset = buffer.Length();
+ buffer.SetLength(buffer.Length() + aNumBytes);
+ return buffer.Elements() + offset;
+ }
+ }
+ mBuffers.AppendElement(nsTArray<uint8_t>(BLOCK_CAPACITY));
+ mBuffers.LastElement().SetLength(aNumBytes);
+ return mBuffers.LastElement().Elements();
+}
+
+} // namespace mozilla