From 8333908df1c582d4d9b0778278542a1887f1ddce Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Mon, 7 May 2018 15:57:56 -0700 Subject: [PATCH] Implemented Id3Tag class to handle ID3 tag Also switched the original code in mp4 to use the new Id3Tag class. Change-Id: I1db2c6c6142ed98b72a432980a6a54815f1a8cc4 --- packager/media/base/id3_tag.cc | 101 +++++++++++++++++ packager/media/base/id3_tag.h | 59 ++++++++++ packager/media/base/id3_tag_unittest.cc | 105 ++++++++++++++++++ packager/media/base/media_base.gyp | 3 + packager/media/formats/mp4/box_definitions.cc | 81 ++------------ packager/media/formats/mp4/box_definitions.h | 16 +-- .../formats/mp4/box_definitions_comparison.h | 6 +- .../formats/mp4/box_definitions_unittest.cc | 5 +- packager/media/formats/mp4/segmenter.cc | 7 +- 9 files changed, 286 insertions(+), 97 deletions(-) create mode 100644 packager/media/base/id3_tag.cc create mode 100644 packager/media/base/id3_tag.h create mode 100644 packager/media/base/id3_tag_unittest.cc diff --git a/packager/media/base/id3_tag.cc b/packager/media/base/id3_tag.cc new file mode 100644 index 0000000000..285bd32cb7 --- /dev/null +++ b/packager/media/base/id3_tag.cc @@ -0,0 +1,101 @@ +// Copyright 2018 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/base/id3_tag.h" + +#include "packager/base/logging.h" +#include "packager/media/base/buffer_writer.h" +#include "packager/media/base/fourccs.h" + +namespace shaka { +namespace media { +namespace { + +// ID3v2 header: http://id3.org/id3v2.4.0-structure. +const char kID3v2Identifier[] = "ID3"; +const uint16_t kID3v2Version = 0x0400; // id3v2.4.0 + +const uint32_t kMaxSynchsafeSize = 0x0FFFFFFF; // 28 effective bits. + +// Convert the specified size into synchsafe integer, where the most significant +// bit (bit 7) is set to zero in every byte. +uint32_t EncodeSynchsafe(uint32_t size) { + return (size & 0x7F) | (((size >> 7) & 0x7F) << 8) | + (((size >> 14) & 0x7F) << 16) | (((size >> 21) & 0x7F) << 24); +} + +bool WriteId3v2Header(uint32_t frames_size, BufferWriter* buffer_writer) { + buffer_writer->AppendString(kID3v2Identifier); + buffer_writer->AppendInt(kID3v2Version); + const uint8_t flags = 0; + buffer_writer->AppendInt(flags); + + if (frames_size > kMaxSynchsafeSize) { + LOG(ERROR) << "Input size (" << frames_size + << ") is out of range (> max synchsafe integer " + << kMaxSynchsafeSize << ")."; + return false; + } + buffer_writer->AppendInt(EncodeSynchsafe(frames_size)); + + return true; +} + +} // namespace + +void Id3Tag::AddPrivateFrame(const std::string& owner, + const std::string& data) { + private_frames_.push_back({owner, data}); +} + +bool Id3Tag::WriteToBuffer(BufferWriter* buffer_writer) { + BufferWriter frames_buffer; + for (const PrivateFrame& private_frame : private_frames_) { + if (!WritePrivateFrame(private_frame, &frames_buffer)) + return false; + } + + if (!WriteId3v2Header(frames_buffer.Size(), buffer_writer)) + return false; + buffer_writer->AppendBuffer(frames_buffer); + return true; +} + +bool Id3Tag::WriteToVector(std::vector* output) { + BufferWriter buffer_writer; + if (!WriteToBuffer(&buffer_writer)) + return false; + buffer_writer.SwapBuffer(output); + return true; +} + +// Implemented per http://id3.org/id3v2.4.0-frames 4.27. +bool Id3Tag::WritePrivateFrame(const PrivateFrame& private_frame, + BufferWriter* buffer_writer) { + buffer_writer->AppendInt(static_cast(FOURCC_PRIV)); + + const uint32_t frame_size = static_cast( + private_frame.owner.size() + 1 + private_frame.data.size()); + if (frame_size > kMaxSynchsafeSize) { + LOG(ERROR) << "Input size (" << frame_size + << ") is out of range (> max synchsafe integer " + << kMaxSynchsafeSize << ")."; + return false; + } + buffer_writer->AppendInt(EncodeSynchsafe(frame_size)); + + const uint16_t flags = 0; + buffer_writer->AppendInt(flags); + + buffer_writer->AppendString(private_frame.owner); + uint8_t byte = 0; // NULL terminating byte between owner and value. + buffer_writer->AppendInt(byte); + buffer_writer->AppendString(private_frame.data); + return true; +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/base/id3_tag.h b/packager/media/base/id3_tag.h new file mode 100644 index 0000000000..62056fe4ef --- /dev/null +++ b/packager/media/base/id3_tag.h @@ -0,0 +1,59 @@ +// Copyright 2018 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_MEDIA_BASE_ID3_TAG_H_ +#define PACKAGER_MEDIA_BASE_ID3_TAG_H_ + +#include +#include + +namespace shaka { +namespace media { + +class BufferWriter; + +/// Implements ID3 tag defined in: http://id3.org/. +/// Only PrivateFrame is supported right now. +class Id3Tag { + public: + Id3Tag() = default; + ~Id3Tag() = default; + + /// Add a "Private Frame". + /// See http://id3.org/id3v2.4.0-frames 4.27. + /// @owner contains the owner identifier. + /// @data contains the data for this private frame. + void AddPrivateFrame(const std::string& owner, const std::string& data); + + /// Write the ID3 tag to a buffer. + /// @param buffer_writer points to the @a BufferWriter to write to. + /// @return true on success. + bool WriteToBuffer(BufferWriter* buffer_writer); + + /// Write the ID3 tag to vector. + /// @param output points to the vector to write to. + /// @return true on success. + bool WriteToVector(std::vector* output); + + private: + Id3Tag(const Id3Tag&) = delete; + Id3Tag& operator=(const Id3Tag&) = delete; + + struct PrivateFrame { + std::string owner; + std::string data; + }; + + bool WritePrivateFrame(const PrivateFrame& private_frame, + BufferWriter* buffer_writer); + + std::vector private_frames_; +}; + +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_BASE_ID3_TAG_H_ diff --git a/packager/media/base/id3_tag_unittest.cc b/packager/media/base/id3_tag_unittest.cc new file mode 100644 index 0000000000..fdbcdf3100 --- /dev/null +++ b/packager/media/base/id3_tag_unittest.cc @@ -0,0 +1,105 @@ +// Copyright 2018 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/base/id3_tag.h" + +#include +#include + +#include "packager/media/base/buffer_writer.h" + +using ::testing::ElementsAreArray; + +namespace shaka { +namespace media { + +TEST(Id3TagTest, WriteToVector) { + Id3Tag id3_tag; + + std::vector output; + id3_tag.WriteToVector(&output); + + const uint8_t kExpectedOutput[] = { + 'I', 'D', '3', 4, 0, 0, 0, 0, 0, 0, + }; + EXPECT_THAT(output, ElementsAreArray(kExpectedOutput)); +} + +TEST(Id3TagTest, WriteToBuffer) { + Id3Tag id3_tag; + + BufferWriter buffer_writer; + id3_tag.WriteToBuffer(&buffer_writer); + + const uint8_t kExpectedOutput[] = { + 'I', 'D', '3', 4, 0, 0, 0, 0, 0, 0, + }; + EXPECT_EQ( + std::vector(std::begin(kExpectedOutput), + std::end(kExpectedOutput)), + std::vector(buffer_writer.Buffer(), + buffer_writer.Buffer() + buffer_writer.Size())); +} + +TEST(Id3TagTest, AddPrivateFrameOnce) { + Id3Tag id3_tag; + id3_tag.AddPrivateFrame("testing.owner", "data"); + + std::vector output; + id3_tag.WriteToVector(&output); + + const uint8_t kExpectedOutput[] = { + 'I', 'D', '3', 4, 0, 0, 0, 0, 0, 28, // Header + 'P', 'R', 'I', 'V', 0, 0, 0, 18, 0, 0, 't', 'e', 's', 't', + 'i', 'n', 'g', '.', 'o', 'w', 'n', 'e', 'r', 0, 'd', 'a', 't', 'a', + }; + EXPECT_THAT(output, ElementsAreArray(kExpectedOutput)); +} + +TEST(Id3TagTest, AddPrivateFrameTwice) { + Id3Tag id3_tag; + id3_tag.AddPrivateFrame("testing.owner1", "data1"); + id3_tag.AddPrivateFrame("testing.owner2", "data2"); + + std::vector output; + id3_tag.WriteToVector(&output); + + const uint8_t kExpectedOutput[] = { + 'I', 'D', '3', 4, 0, 0, 0, 0, 0, 60, // Header + 'P', 'R', 'I', 'V', 0, 0, 0, 20, 0, 0, 't', 'e', 's', 't', 'i', + 'n', 'g', '.', 'o', 'w', 'n', 'e', 'r', '1', 0, 'd', 'a', 't', 'a', '1', + 'P', 'R', 'I', 'V', 0, 0, 0, 20, 0, 0, 't', 'e', 's', 't', 'i', + 'n', 'g', '.', 'o', 'w', 'n', 'e', 'r', '2', 0, 'd', 'a', 't', 'a', '2', + }; + EXPECT_THAT(output, ElementsAreArray(kExpectedOutput)); +} + +TEST(Id3TagTest, AddBigPrivateFrameOnce) { + const size_t kTestOwnerSize = 200; + const size_t kTestDataSize = 200; + std::string test_owner(kTestOwnerSize, 0); + std::string test_data(kTestDataSize, 0); + + Id3Tag id3_tag; + id3_tag.AddPrivateFrame(test_owner, test_data); + + std::vector output; + id3_tag.WriteToVector(&output); + + const uint8_t kExpectedOutputHead[] = { + 'I', 'D', '3', 4, 0, 0, 0, 0, 3, 27, // Header + 'P', 'R', 'I', 'V', 0, 0, 3, 17, 0, 0, + }; + std::vector expected_output(std::begin(kExpectedOutputHead), + std::end(kExpectedOutputHead)); + expected_output.resize(expected_output.size() + kTestOwnerSize + 1 + + kTestDataSize); + EXPECT_EQ(expected_output, output); +} + +} // namespace media + +} // namespace shaka diff --git a/packager/media/base/media_base.gyp b/packager/media/base/media_base.gyp index 3cb145bf40..268c69ee4f 100644 --- a/packager/media/base/media_base.gyp +++ b/packager/media/base/media_base.gyp @@ -47,6 +47,8 @@ 'fourccs.h', 'http_key_fetcher.cc', 'http_key_fetcher.h', + 'id3_tag.cc', + 'id3_tag.h', 'key_fetcher.cc', 'key_fetcher.h', 'key_source.cc', @@ -171,6 +173,7 @@ 'container_names_unittest.cc', 'decryptor_source_unittest.cc', 'http_key_fetcher_unittest.cc', + 'id3_tag_unittest.cc', 'muxer_util_unittest.cc', 'offset_byte_queue_unittest.cc', 'producer_consumer_queue_unittest.cc', diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 8d0fed6995..be58fda345 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -67,11 +67,6 @@ bool IsIvSizeValid(uint8_t per_sample_iv_size) { // bit(5) Reserved // 0 const uint8_t kDdtsExtraData[] = {0xe4, 0x7c, 0, 4, 0, 0x0f, 0}; -// ID3v2 header: http://id3.org/id3v2.4.0-structure -const uint32_t kID3v2HeaderSize = 10; -const char kID3v2Identifier[] = "ID3"; -const uint16_t kID3v2Version = 0x0400; // id3v2.4.0 - // Utility functions to check if the 64bit integers can fit in 32bit integer. bool IsFitIn32Bits(uint64_t a) { return a <= std::numeric_limits::max(); @@ -1328,84 +1323,24 @@ uint32_t Language::ComputeSize() const { return 2; } -bool PrivFrame::ReadWrite(BoxBuffer* buffer) { - FourCC fourcc = FOURCC_PRIV; - RCHECK(buffer->ReadWriteFourCC(&fourcc)); - if (fourcc != FOURCC_PRIV) { - VLOG(1) << "Skip unrecognized id3 frame during read: " - << FourCCToString(fourcc); - return true; - } - - uint32_t frame_size = static_cast(owner.size() + 1 + value.size()); - // size should be encoded as synchsafe integer, which is not support here. - // We don't expect frame_size to be larger than 0x7F. Synchsafe integers less - // than 0x7F is encoded in the same way as normal integer. - DCHECK_LT(frame_size, 0x7Fu); - uint16_t flags = 0; - RCHECK(buffer->ReadWriteUInt32(&frame_size) && - buffer->ReadWriteUInt16(&flags)); - - if (buffer->Reading()) { - std::string str; - RCHECK(buffer->ReadWriteString(&str, frame_size)); - // |owner| is null terminated. - size_t pos = str.find('\0'); - RCHECK(pos < str.size()); - owner = str.substr(0, pos); - value = str.substr(pos + 1); - } else { - uint8_t byte = 0; // Null terminating byte between owner and value. - RCHECK(buffer->ReadWriteString(&owner, owner.size()) && - buffer->ReadWriteUInt8(&byte) && - buffer->ReadWriteString(&value, value.size())); - } - return true; -} - -uint32_t PrivFrame::ComputeSize() const { - if (owner.empty() && value.empty()) - return 0; - const uint32_t kFourCCSize = 4; - return kFourCCSize + - static_cast(sizeof(uint32_t) + sizeof(uint16_t) + - owner.size() + 1 + value.size()); -} - ID3v2::ID3v2() {} ID3v2::~ID3v2() {} FourCC ID3v2::BoxType() const { return FOURCC_ID32; } bool ID3v2::ReadWriteInternal(BoxBuffer* buffer) { - RCHECK(ReadWriteHeaderInternal(buffer) && - language.ReadWrite(buffer)); - - // Read/Write ID3v2 header - std::string id3v2_identifier = kID3v2Identifier; - uint16_t version = kID3v2Version; - // We only support PrivateFrame in ID3. - uint32_t data_size = private_frame.ComputeSize(); - // size should be encoded as synchsafe integer, which is not support here. - // We don't expect data_size to be larger than 0x7F. Synchsafe integers less - // than 0x7F is encoded in the same way as normal integer. - DCHECK_LT(data_size, 0x7Fu); - uint8_t flags = 0; - RCHECK(buffer->ReadWriteString(&id3v2_identifier, id3v2_identifier.size()) && - buffer->ReadWriteUInt16(&version) && - buffer->ReadWriteUInt8(&flags) && - buffer->ReadWriteUInt32(&data_size)); - - RCHECK(private_frame.ReadWrite(buffer)); + RCHECK(ReadWriteHeaderInternal(buffer) && language.ReadWrite(buffer) && + buffer->ReadWriteVector(&id3v2_data, buffer->Reading() + ? buffer->BytesLeft() + : id3v2_data.size())); return true; } size_t ID3v2::ComputeSizeInternal() { - uint32_t private_frame_size = private_frame.ComputeSize(); - // Skip ID3v2 box generation if there is no private frame. - return private_frame_size == 0 ? 0 : HeaderSize() + language.ComputeSize() + - kID3v2HeaderSize + - private_frame_size; + // Skip ID3v2 box generation if there is no id3 data. + return id3v2_data.size() == 0 + ? 0 + : HeaderSize() + language.ComputeSize() + id3v2_data.size(); } Metadata::Metadata() {} diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index 2757a9685d..829c8557c2 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -237,24 +237,12 @@ struct Language { std::string code; }; -/// Implemented per http://id3.org/id3v2.4.0-frames. -struct PrivFrame { - bool ReadWrite(BoxBuffer* buffer); - uint32_t ComputeSize() const; - - std::string owner; - std::string value; -}; - -/// Implemented per http://mp4ra.org/specs.html#id3v2 and -/// http://id3.org/id3v2.4.0-structure. +/// Implemented per http://mp4ra.org/#/references. struct ID3v2 : FullBox { DECLARE_BOX_METHODS(ID3v2); Language language; - - /// We only support PrivateFrame in ID3. Other frames are ignored. - PrivFrame private_frame; + std::vector id3v2_data; }; struct Metadata : FullBox { diff --git a/packager/media/formats/mp4/box_definitions_comparison.h b/packager/media/formats/mp4/box_definitions_comparison.h index d866a543d1..9e2be24431 100644 --- a/packager/media/formats/mp4/box_definitions_comparison.h +++ b/packager/media/formats/mp4/box_definitions_comparison.h @@ -229,12 +229,8 @@ inline bool operator==(const Language& lhs, return lhs.code == rhs.code; } -inline bool operator==(const PrivFrame& lhs, const PrivFrame& rhs) { - return lhs.owner == rhs.owner && lhs.value == rhs.value; -} - inline bool operator==(const ID3v2& lhs, const ID3v2& rhs) { - return lhs.language == rhs.language && lhs.private_frame == rhs.private_frame; + return lhs.language == rhs.language && lhs.id3v2_data == rhs.id3v2_data; } inline bool operator==(const Metadata& lhs, const Metadata& rhs) { diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index ea2e0f1bd8..0883704f74 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -309,13 +309,12 @@ class BoxDefinitionsTestGeneral : public testing::Test { void Fill(ID3v2* id3v2) { id3v2->language.code = "eng"; - id3v2->private_frame.owner = "shaka-packager"; - id3v2->private_frame.value = "version 1.2.0-debug"; + id3v2->id3v2_data.assign(std::begin(kData16Bytes), std::end(kData16Bytes)); } void Modify(ID3v2* id3v2) { id3v2->language.code = "fre"; - id3v2->private_frame.value = "version 1.3.1-release"; + id3v2->id3v2_data.assign(std::begin(kData8Bytes), std::end(kData8Bytes)); } void Fill(Metadata* metadata) { diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index e736f67171..e4f4bf4227 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -10,6 +10,7 @@ #include "packager/base/logging.h" #include "packager/media/base/buffer_writer.h" +#include "packager/media/base/id3_tag.h" #include "packager/media/base/media_sample.h" #include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_util.h" @@ -92,8 +93,10 @@ Status Segmenter::Initialize( if (!version.empty()) { moov_->metadata.handler.handler_type = FOURCC_ID32; moov_->metadata.id3v2.language.code = "eng"; - moov_->metadata.id3v2.private_frame.owner = GetPackagerProjectUrl(); - moov_->metadata.id3v2.private_frame.value = version; + + Id3Tag id3_tag; + id3_tag.AddPrivateFrame(GetPackagerProjectUrl(), version); + CHECK(id3_tag.WriteToVector(&moov_->metadata.id3v2.id3v2_data)); } return DoInitialize(); }