From 3318ad715d7955d5635a02f8ed16dfd7c0ae68fe Mon Sep 17 00:00:00 2001 From: Kongqun Yang Date: Fri, 22 Nov 2013 13:24:25 -0800 Subject: [PATCH] Define BoxBuffer which wraps mp4 box read/write. BoxBuffer wraps either BoxReader for reading or BufferWriter for writing. Thus it is capable of doing either reading or writing, but not both. Change-Id: Id57370755a586bfdef1291a23af29f5b1feec903 --- media/mp4/box_buffer.h | 176 +++++++++++++++++++++++++++++++ media/mp4/box_reader.cc | 73 ++++++------- media/mp4/box_reader.h | 77 +++++++------- media/mp4/box_reader_unittest.cc | 111 ++++++++++--------- 4 files changed, 305 insertions(+), 132 deletions(-) create mode 100644 media/mp4/box_buffer.h diff --git a/media/mp4/box_buffer.h b/media/mp4/box_buffer.h new file mode 100644 index 0000000000..101e915e8c --- /dev/null +++ b/media/mp4/box_buffer.h @@ -0,0 +1,176 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MP4_BOX_BUFFER_H_ +#define MEDIA_MP4_BOX_BUFFER_H_ + +#include "base/compiler_specific.h" +#include "media/base/buffer_writer.h" +#include "media/mp4/box.h" +#include "media/mp4/box_reader.h" + +namespace media { +namespace mp4 { + +// Defines a wrapper for mp4 box reading/writing, which is symmetric in most +// cases, i.e. we can use one single routine for the reading and writing. +// BoxBuffer wraps either BoxReader for reading or BufferWriter for writing. +// Thus it is capable of doing either reading or writing, but not both. +class BoxBuffer { + public: + // Creates a "reader" version of the BoxBuffer. + // Caller retains |reader| ownership. |reader| should not be NULL. + explicit BoxBuffer(BoxReader* reader) : reader_(reader), writer_(NULL) { + DCHECK(reader); + } + // Creates a "writer" version of the BoxBuffer. + // Caller retains |writer| ownership. |writer| should not be NULL. + explicit BoxBuffer(BufferWriter* writer) : reader_(NULL), writer_(writer) { + DCHECK(writer); + } + ~BoxBuffer() {} + + // Reading or writing? + bool Reading() const { return reader_ != NULL; } + + // Returns current read/write position. In read mode, this is the current + // read position. In write mode, it is the same as Size(). + size_t Pos() const { + if (reader_) + return reader_->pos(); + return writer_->Size(); + } + + // Returns total buffer size.In read mode, it includes data that has already + // been read or skipped, and will not change. In write mode, it includes all + // data that has been written, and will change as data is written. + size_t Size() const { + if (reader_) + return reader_->size(); + return writer_->Size(); + } + + // Read/write integers of various size and unsigned/signed. + bool ReadWriteUInt8(uint8* v) { + if (reader_) + return reader_->Read1(v); + writer_->AppendInt(*v); + return true; + } + bool ReadWriteUInt16(uint16* v) { + if (reader_) + return reader_->Read2(v); + writer_->AppendInt(*v); + return true; + } + bool ReadWriteUInt32(uint32* v) { + if (reader_) + return reader_->Read4(v); + writer_->AppendInt(*v); + return true; + } + bool ReadWriteUInt64(uint64* v) { + if (reader_) + return reader_->Read8(v); + writer_->AppendInt(*v); + return true; + } + bool ReadWriteInt16(int16* v) { + if (reader_) + return reader_->Read2s(v); + writer_->AppendInt(*v); + return true; + } + bool ReadWriteInt32(int32* v) { + if (reader_) + return reader_->Read4s(v); + writer_->AppendInt(*v); + return true; + } + bool ReadWriteInt64(int64* v) { + if (reader_) + return reader_->Read8s(v); + writer_->AppendInt(*v); + return true; + } + + // Read/write the least significant |num_bytes| of |v| from/to buffer. + // |num_bytes| should not be larger than sizeof(v), i.e. 8. + bool ReadWriteUInt64NBytes(uint64* v, size_t num_bytes) { + if (reader_) + return reader_->ReadNBytesInto8(v, num_bytes); + writer_->AppendNBytes(*v, num_bytes); + return true; + } + bool ReadWriteInt64NBytes(int64* v, size_t num_bytes) { + if (reader_) + return reader_->ReadNBytesInto8s(v, num_bytes); + writer_->AppendNBytes(*v, num_bytes); + return true; + } + bool ReadWriteVector(std::vector* vector, size_t count) { + if (reader_) + return reader_->ReadToVector(vector, count); + DCHECK_EQ(vector->size(), count); + writer_->AppendVector(*vector); + return true; + } + bool ReadWriteFourCC(FourCC* fourcc) { + if (reader_) + return reader_->ReadFourCC(fourcc); + writer_->AppendInt(static_cast(*fourcc)); + return true; + } + + // Prepare child boxes for read/write. + bool PrepareChildren() { + if (reader_) + return reader_->ScanChildren(); + // NOP in write mode. + return true; + } + + // Read/write child box. + bool ReadWriteChild(Box* box) { + if (reader_) + return reader_->ReadChild(box); + // The box is mandatory, i.e. the box size should not be 0. + DCHECK_NE(0, box->atom_size); + CHECK(box->ReadWrite(this)); + return true; + } + + // Read/write child box if exist. + bool TryReadWriteChild(Box* box) { + if (reader_) + return reader_->TryReadChild(box); + // The box is optional, i.e. it can be skipped if the box size is 0. + if (box->atom_size != 0) + CHECK(box->ReadWrite(this)); + return true; + } + + // Skip |num_bytes| in read mode, otherwise fill with |num_bytes| of '\0'. + bool IgnoreBytes(size_t num_bytes) { + if (reader_) + return reader_->SkipBytes(num_bytes); + std::vector vector(num_bytes, 0); + writer_->AppendVector(vector); + return true; + } + + BoxReader* reader() { return reader_; } + BufferWriter* writer() { return writer_; } + + private: + BoxReader* reader_; + BufferWriter* writer_; + + DISALLOW_COPY_AND_ASSIGN(BoxBuffer); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_BOX_BUFFER_H_ diff --git a/media/mp4/box_reader.cc b/media/mp4/box_reader.cc index b6b3282229..2d7689c76b 100644 --- a/media/mp4/box_reader.cc +++ b/media/mp4/box_reader.cc @@ -4,34 +4,25 @@ #include "media/mp4/box_reader.h" -#include -#include -#include -#include - #include "base/logging.h" #include "base/memory/scoped_ptr.h" -#include "media/mp4/box_definitions.h" -#include "media/mp4/rcheck.h" +#include "media/mp4/box.h" namespace media { namespace mp4 { -Box::~Box() {} - -BoxReader::BoxReader(const uint8* buf, const int size) - : BufferReader(buf, size), - type_(FOURCC_NULL), - version_(0), - flags_(0), - scanned_(false) { +BoxReader::BoxReader(const uint8* buf, size_t size) + : BufferReader(buf, size), type_(FOURCC_NULL), scanned_(false) { + DCHECK(buf); + DCHECK_LT(0, size); } BoxReader::~BoxReader() { if (scanned_ && !children_.empty()) { - for (ChildMap::iterator itr = children_.begin(); - itr != children_.end(); ++itr) { + for (ChildMap::iterator itr = children_.begin(); itr != children_.end(); + ++itr) { DVLOG(1) << "Skipping unknown box: " << FourCCToString(itr->first); + delete itr->second; } } } @@ -62,7 +53,8 @@ bool BoxReader::StartTopLevelBox(const uint8* buf, int* box_size, bool* err) { BoxReader reader(buf, buf_size); - if (!reader.ReadHeader(err)) return false; + if (!reader.ReadHeader(err)) + return false; if (!IsValidTopLevelBox(reader.type())) { *err = true; return false; @@ -102,17 +94,20 @@ bool BoxReader::ScanChildren() { DCHECK(!scanned_); scanned_ = true; - bool err = false; while (pos() < size()) { - BoxReader child(&buf_[pos_], size_ - pos_); - if (!child.ReadHeader(&err)) break; + scoped_ptr child( + new BoxReader(&data()[pos()], size() - pos())); + bool err; + if (!child->ReadHeader(&err)) + return false; - children_.insert(std::pair(child.type(), child)); - pos_ += child.size(); + FourCC box_type = child->type(); + size_t box_size = child->size(); + children_.insert(std::pair(box_type, child.release())); + RCHECK(SkipBytes(box_size)); } - DCHECK(!err); - return !err && pos() == size(); + return true; } bool BoxReader::ReadChild(Box* child) { @@ -122,7 +117,8 @@ bool BoxReader::ReadChild(Box* child) { ChildMap::iterator itr = children_.find(child_type); RCHECK(itr != children_.end()); DVLOG(2) << "Found a " << FourCCToString(child_type) << " box."; - RCHECK(child->Parse(&itr->second)); + RCHECK(child->Parse(itr->second)); + delete itr->second; children_.erase(itr); return true; } @@ -131,38 +127,31 @@ bool BoxReader::ChildExist(Box* child) { return children_.count(child->BoxType()) > 0; } -bool BoxReader::MaybeReadChild(Box* child) { - if (!children_.count(child->BoxType())) return true; +bool BoxReader::TryReadChild(Box* child) { + if (!children_.count(child->BoxType())) + return true; return ReadChild(child); } -bool BoxReader::ReadFullBoxHeader() { - uint32 vflags; - RCHECK(Read4(&vflags)); - version_ = vflags >> 24; - flags_ = vflags & 0xffffff; - return true; -} - bool BoxReader::ReadHeader(bool* err) { uint64 size = 0; *err = false; - if (!HasBytes(8)) return false; - CHECK(Read4Into8(&size) && ReadFourCC(&type_)); + if (!ReadNBytesInto8(&size, sizeof(uint32)) || !ReadFourCC(&type_)) + return false; if (size == 0) { // Media Source specific: we do not support boxes that run to EOS. *err = true; return false; } else if (size == 1) { - if (!HasBytes(8)) return false; - CHECK(Read8(&size)); + if (!Read8(&size)) + return false; } // Implementation-specific: support for boxes larger than 2^31 has been // removed. - if (size < static_cast(pos_) || + if (size < static_cast(pos()) || size > static_cast(kint32max)) { *err = true; return false; @@ -170,7 +159,7 @@ bool BoxReader::ReadHeader(bool* err) { // Note that the pos_ head has advanced to the byte immediately after the // header, which is where we want it. - size_ = size; + set_size(size); return true; } diff --git a/media/mp4/box_reader.h b/media/mp4/box_reader.h index 33a2ec1f21..528fa0c17c 100644 --- a/media/mp4/box_reader.h +++ b/media/mp4/box_reader.h @@ -17,13 +17,7 @@ namespace media { namespace mp4 { -class BoxReader; - -struct Box { - virtual ~Box(); - virtual bool Parse(BoxReader* reader) = 0; - virtual FourCC BoxType() const = 0; -}; +class Box; class BoxReader : public BufferReader { public: @@ -69,33 +63,34 @@ class BoxReader : public BufferReader { // Read one child if available. Returns false on error, true on successful // read or on child absent. - bool MaybeReadChild(Box* child) WARN_UNUSED_RESULT; + bool TryReadChild(Box* child) WARN_UNUSED_RESULT; // Read at least one child. False means error or no such child present. - template bool ReadChildren( - std::vector* children) WARN_UNUSED_RESULT; + template + bool ReadChildren(std::vector* children) WARN_UNUSED_RESULT; // Read any number of children. False means error. - template bool MaybeReadChildren( - std::vector* children) WARN_UNUSED_RESULT; + template + bool TryReadChildren(std::vector* children) WARN_UNUSED_RESULT; // Read all children, regardless of FourCC. This is used from exactly one box, // corresponding to a rather significant inconsistency in the BMFF spec. // Note that this method is mutually exclusive with ScanChildren(). - template bool ReadAllChildren( - std::vector* children) WARN_UNUSED_RESULT; + template + bool ReadAllChildren(std::vector* children) WARN_UNUSED_RESULT; - // Populate the values of 'version()' and 'flags()' from a full box header. - // Many boxes, but not all, use these values. This call should happen after - // the box has been initialized, and does not re-read the main box header. - bool ReadFullBoxHeader() WARN_UNUSED_RESULT; + bool ReadFourCC(FourCC* fourcc) { + uint32 val; + if (!Read4(&val)) + return false; + *fourcc = static_cast(val); + return true; + } - FourCC type() const { return type_; } - uint8 version() const { return version_; } - uint32 flags() const { return flags_; } + FourCC type() const { return type_; } private: - BoxReader(const uint8* buf, const int size); + BoxReader(const uint8* buf, size_t size); // Must be called immediately after init. If the return is false, this // indicates that the box header and its contents were not available in the @@ -107,25 +102,26 @@ class BoxReader : public BufferReader { bool ReadHeader(bool* err); FourCC type_; - uint8 version_; - uint32 flags_; - typedef std::multimap ChildMap; + typedef std::multimap ChildMap; // The set of child box FourCCs and their corresponding buffer readers. Only // valid if scanned_ is true. ChildMap children_; bool scanned_; + + DISALLOW_COPY_AND_ASSIGN(BoxReader); }; -// Template definitions -template bool BoxReader::ReadChildren(std::vector* children) { - RCHECK(MaybeReadChildren(children) && !children->empty()); +// Template definitions. +template +bool BoxReader::ReadChildren(std::vector* children) { + RCHECK(TryReadChildren(children) && !children->empty()); return true; } -template -bool BoxReader::MaybeReadChildren(std::vector* children) { +template +bool BoxReader::TryReadChildren(std::vector* children) { DCHECK(scanned_); DCHECK(children->empty()); @@ -137,32 +133,35 @@ bool BoxReader::MaybeReadChildren(std::vector* children) { children->resize(std::distance(start_itr, end_itr)); typename std::vector::iterator child_itr = children->begin(); for (ChildMap::iterator itr = start_itr; itr != end_itr; ++itr) { - RCHECK(child_itr->Parse(&itr->second)); + RCHECK(child_itr->Parse(itr->second)); + delete itr->second; ++child_itr; } children_.erase(start_itr, end_itr); - DVLOG(2) << "Found " << children->size() << " " - << FourCCToString(child_type) << " boxes."; + DVLOG(2) << "Found " << children->size() << " " << FourCCToString(child_type) + << " boxes."; return true; } -template +template bool BoxReader::ReadAllChildren(std::vector* children) { DCHECK(!scanned_); scanned_ = true; - bool err = false; while (pos() < size()) { - BoxReader child_reader(&buf_[pos_], size_ - pos_); - if (!child_reader.ReadHeader(&err)) break; + BoxReader child_reader(&data()[pos()], size() - pos()); + bool err; + if (!child_reader.ReadHeader(&err)) + return false; + T child; RCHECK(child.Parse(&child_reader)); children->push_back(child); - pos_ += child_reader.size(); + RCHECK(SkipBytes(child_reader.size())); } - return !err; + return true; } } // namespace mp4 diff --git a/media/mp4/box_reader_unittest.cc b/media/mp4/box_reader_unittest.cc index 40af435f1b..1816bf6584 100644 --- a/media/mp4/box_reader_unittest.cc +++ b/media/mp4/box_reader_unittest.cc @@ -7,7 +7,7 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" -#include "media/mp4/box_reader.h" +#include "media/mp4/box_buffer.h" #include "media/mp4/rcheck.h" #include "testing/gtest/include/gtest/gtest.h" @@ -15,66 +15,75 @@ namespace media { namespace mp4 { static const uint8 kSkipBox[] = { - // Top-level test box containing three children - 0x00, 0x00, 0x00, 0x40, 's', 'k', 'i', 'p', - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0xf9, 0x0a, 0x0b, 0x0c, 0xfd, 0x0e, 0x0f, 0x10, - // Ordinary (8-byte header) child box - 0x00, 0x00, 0x00, 0x0c, 'p', 's', 's', 'h', 0xde, 0xad, 0xbe, 0xef, - // Extended-size header child box - 0x00, 0x00, 0x00, 0x01, 'p', 's', 's', 'h', - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, - 0xfa, 0xce, 0xca, 0xfe, - // Empty free box - 0x00, 0x00, 0x00, 0x08, 'f', 'r', 'e', 'e', - // Trailing garbage - 0x00 }; + // Top-level test box containing three children. + 0x00, 0x00, 0x00, 0x40, 's', 'k', 'i', 'p', 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0xf9, 0x0a, 0x0b, 0x0c, 0xfd, 0x0e, 0x0f, 0x10, + // Ordinary (8-byte header) child box. + 0x00, 0x00, 0x00, 0x0c, 'p', 's', 's', 'h', 0xde, 0xad, 0xbe, 0xef, + // Extended-size header child box. + 0x00, 0x00, 0x00, 0x01, 'p', 's', 's', 'h', 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x14, 0xfa, 0xce, 0xca, 0xfe, + // Empty free box. + 0x00, 0x00, 0x00, 0x08, 'f', 'r', 'e', 'e', + // Trailing garbage. + 0x00}; struct FreeBox : Box { - virtual bool Parse(BoxReader* reader) OVERRIDE { + virtual bool ReadWrite(BoxBuffer* buffer) OVERRIDE { return true; } virtual FourCC BoxType() const OVERRIDE { return FOURCC_FREE; } + virtual uint32 ComputeSize() { + NOTIMPLEMENTED(); + return 0; + } }; struct PsshBox : Box { - uint32 val; - - virtual bool Parse(BoxReader* reader) OVERRIDE { - return reader->Read4(&val); + virtual bool ReadWrite(BoxBuffer* buffer) OVERRIDE { + return buffer->ReadWriteUInt32(&val); } virtual FourCC BoxType() const OVERRIDE { return FOURCC_PSSH; } + virtual uint32 ComputeSize() { + NOTIMPLEMENTED(); + return 0; + } + + uint32 val; }; -struct SkipBox : Box { +struct SkipBox : FullBox { + virtual bool ReadWrite(BoxBuffer* buffer) OVERRIDE { + RCHECK(FullBox::ReadWrite(buffer) && + buffer->ReadWriteUInt8(&a) && + buffer->ReadWriteUInt8(&b) && + buffer->ReadWriteUInt16(&c) && + buffer->ReadWriteInt32(&d) && + buffer->ReadWriteInt64NBytes(&e, sizeof(uint32))); + RCHECK(buffer->PrepareChildren()); + if (buffer->Reading()) { + DCHECK(buffer->reader()); + RCHECK(buffer->reader()->ReadChildren(&kids)); + } else { + NOTIMPLEMENTED(); + } + return buffer->TryReadWriteChild(&empty); + } + virtual FourCC BoxType() const OVERRIDE { return FOURCC_SKIP; } + virtual uint32 ComputeSize() { + NOTIMPLEMENTED(); + return 0; + } + uint8 a, b; uint16 c; int32 d; int64 e; std::vector kids; - FreeBox mpty; - - virtual bool Parse(BoxReader* reader) OVERRIDE { - RCHECK(reader->ReadFullBoxHeader() && - reader->Read1(&a) && - reader->Read1(&b) && - reader->Read2(&c) && - reader->Read4s(&d) && - reader->Read4sInto8s(&e)); - return reader->ScanChildren() && - reader->ReadChildren(&kids) && - reader->MaybeReadChild(&mpty); - } - virtual FourCC BoxType() const OVERRIDE { return FOURCC_SKIP; } - - SkipBox(); - virtual ~SkipBox(); + FreeBox empty; }; -SkipBox::SkipBox() {} -SkipBox::~SkipBox() {} - class BoxReaderTest : public testing::Test { protected: std::vector GetBuf() { @@ -92,8 +101,8 @@ TEST_F(BoxReaderTest, ExpectedOperationTest) { SkipBox box; EXPECT_TRUE(box.Parse(reader.get())); - EXPECT_EQ(0x01, reader->version()); - EXPECT_EQ(0x020304u, reader->flags()); + EXPECT_EQ(0x01, box.version); + EXPECT_EQ(0x020304u, box.flags); EXPECT_EQ(0x05, box.a); EXPECT_EQ(0x06, box.b); EXPECT_EQ(0x0708, box.c); @@ -104,7 +113,7 @@ TEST_F(BoxReaderTest, ExpectedOperationTest) { EXPECT_EQ(0xdeadbeef, box.kids[0].val); EXPECT_EQ(0xfacecafe, box.kids[1].val); - // Accounting for the extra byte outside of the box above + // Accounting for the extra byte outside of the box above. EXPECT_EQ(buf.size(), static_cast(reader->size() + 1)); } @@ -156,7 +165,7 @@ TEST_F(BoxReaderTest, ScanChildrenTest) { FreeBox free; EXPECT_TRUE(reader->ReadChild(&free)); EXPECT_FALSE(reader->ReadChild(&free)); - EXPECT_TRUE(reader->MaybeReadChild(&free)); + EXPECT_TRUE(reader->TryReadChild(&free)); std::vector kids; @@ -164,12 +173,12 @@ TEST_F(BoxReaderTest, ScanChildrenTest) { EXPECT_EQ(2u, kids.size()); kids.clear(); EXPECT_FALSE(reader->ReadChildren(&kids)); - EXPECT_TRUE(reader->MaybeReadChildren(&kids)); + EXPECT_TRUE(reader->TryReadChildren(&kids)); } TEST_F(BoxReaderTest, ReadAllChildrenTest) { std::vector buf = GetBuf(); - // Modify buffer to exclude its last 'free' box + // Modify buffer to exclude its last 'free' box. buf[3] = 0x38; bool err; scoped_ptr reader( @@ -178,13 +187,13 @@ TEST_F(BoxReaderTest, ReadAllChildrenTest) { std::vector kids; EXPECT_TRUE(reader->SkipBytes(16) && reader->ReadAllChildren(&kids)); EXPECT_EQ(2u, kids.size()); - EXPECT_EQ(kids[0].val, 0xdeadbeef); // Ensure order is preserved + EXPECT_EQ(kids[0].val, 0xdeadbeef); // Ensure order is preserved. } TEST_F(BoxReaderTest, SkippingBloc) { - static const uint8 kData[] = { - 0x00, 0x00, 0x00, 0x09, 'b', 'l', 'o', 'c', 0x00 - }; + static const uint8 kData[] = {0x00, 0x00, 0x00, 0x09, // Box size. + 'b', 'l', 'o', 'c', // FourCC. + 0x00}; // Reserved byte. std::vector buf(kData, kData + sizeof(kData));