diff --git a/packager/media/base/bit_reader.cc b/packager/media/base/bit_reader.cc index 50fc6c3c70..517f830474 100644 --- a/packager/media/base/bit_reader.cc +++ b/packager/media/base/bit_reader.cc @@ -53,6 +53,21 @@ bool BitReader::SkipBits(int num_bits) { return ReadBitsInternal(num_bits, ¬_needed); } +bool BitReader::SkipBytes(int num_bytes) { + if (num_remaining_bits_in_curr_byte_ != 8) + return false; + if (num_bytes == 0) + return true; + + data_ += num_bytes - 1; // One additional byte in curr_byte_. + if (num_bytes > bytes_left_ + 1) + return false; + bytes_left_ -= num_bytes - 1; + num_remaining_bits_in_curr_byte_ = 0; + UpdateCurrByte(); + return true; +} + bool BitReader::ReadBitsInternal(int num_bits, uint64_t* out) { DCHECK_LE(num_bits, 64); diff --git a/packager/media/base/bit_reader.h b/packager/media/base/bit_reader.h index bf00f9b560..92e06bbd37 100644 --- a/packager/media/base/bit_reader.h +++ b/packager/media/base/bit_reader.h @@ -44,10 +44,34 @@ class BitReader { /// @param num_bits specifies the number of bits to be skipped. /// @return false if the given number of bits cannot be skipped (not enough /// bits in the stream), true otherwise. When false is returned, the - /// stream will enter a state where further ReadBits/SkipBits - /// operations will always return false unless |num_bits| is 0. + /// stream will enter a state where further ReadXXX/SkipXXX + /// operations will always return false unless |num_bits/bytes| is 0. bool SkipBits(int num_bits); + /// Read one bit then skip the number of bits specified if that bit matches @a + /// condition. + /// @param condition indicates when the number of bits should be skipped. + /// @param num_bits specifies the number of bits to be skipped. + /// @return false if the one bit cannot be read (not enough bits in the + /// stream) or if the bit is set but the given number of bits cannot + /// be skipped (not enough bits in the stream), true otherwise. When + /// false is returned, the stream will enter a state where further + /// ReadXXX/SkipXXX operations will always return false. + bool SkipBitsConditional(bool condition, int num_bits) { + bool condition_read = true; + if (!ReadBits(1, &condition_read)) + return false; + return condition_read == condition ? SkipBits(num_bits) : true; + } + + /// Skip a number of bytes from stream. The current posision should be byte + /// aligned, otherwise a false is returned and bytes are not skipped. + /// @param num_bytes specifies the number of bytes to be skipped. + /// @return false if the current position is not byte aligned or if the given + /// number of bytes cannot be skipped (not enough bytes in the + /// stream), true otherwise. + bool SkipBytes(int num_bytes); + /// @return The number of bits available for reading. int bits_available() const { return 8 * bytes_left_ + num_remaining_bits_in_curr_byte_; diff --git a/packager/media/base/bit_reader_unittest.cc b/packager/media/base/bit_reader_unittest.cc index b07541e6b7..e79ea26807 100644 --- a/packager/media/base/bit_reader_unittest.cc +++ b/packager/media/base/bit_reader_unittest.cc @@ -17,20 +17,22 @@ TEST(BitReaderTest, NormalOperationTest) { BitReader reader1(buffer, 6); // Initialize with 6 bytes only EXPECT_TRUE(reader1.ReadBits(1, &value8)); - EXPECT_EQ(value8, 0); + EXPECT_EQ(0, value8); EXPECT_TRUE(reader1.ReadBits(8, &value8)); - EXPECT_EQ(value8, 0xab); // 1010 1011 + EXPECT_EQ(0xab, value8); // 1010 1011 + EXPECT_EQ(39, reader1.bits_available()); + EXPECT_EQ(9, reader1.bit_position()); EXPECT_TRUE(reader1.ReadBits(7, &value64)); EXPECT_TRUE(reader1.ReadBits(32, &value64)); - EXPECT_EQ(value64, 0x55995599u); + EXPECT_EQ(0x55995599u, value64); EXPECT_FALSE(reader1.ReadBits(1, &value8)); value8 = 0xff; EXPECT_TRUE(reader1.ReadBits(0, &value8)); - EXPECT_EQ(value8, 0); + EXPECT_EQ(0, value8); BitReader reader2(buffer, 8); EXPECT_TRUE(reader2.ReadBits(64, &value64)); - EXPECT_EQ(value64, 0x5599559955995599ull); + EXPECT_EQ(0x5599559955995599ull, value64); EXPECT_FALSE(reader2.ReadBits(1, &value8)); EXPECT_TRUE(reader2.ReadBits(0, &value8)); } @@ -53,17 +55,38 @@ TEST(BitReaderTest, SkipBitsTest) { EXPECT_TRUE(reader1.SkipBits(2)); EXPECT_TRUE(reader1.ReadBits(3, &value8)); - EXPECT_EQ(value8, 1); + EXPECT_EQ(1, value8); + EXPECT_FALSE(reader1.SkipBytes(1)); // not aligned. EXPECT_TRUE(reader1.SkipBits(11)); EXPECT_TRUE(reader1.ReadBits(8, &value8)); - EXPECT_EQ(value8, 3); - EXPECT_TRUE(reader1.SkipBits(76)); + EXPECT_EQ(3, value8); + EXPECT_TRUE(reader1.SkipBytes(2)); + EXPECT_TRUE(reader1.SkipBytes(0)); + EXPECT_TRUE(reader1.SkipBytes(1)); + EXPECT_TRUE(reader1.SkipBits(52)); + EXPECT_EQ(20, reader1.bits_available()); + EXPECT_EQ(100, reader1.bit_position()); EXPECT_TRUE(reader1.ReadBits(4, &value8)); - EXPECT_EQ(value8, 13); + EXPECT_EQ(13, value8); EXPECT_FALSE(reader1.SkipBits(100)); EXPECT_TRUE(reader1.SkipBits(0)); EXPECT_FALSE(reader1.SkipBits(1)); } +TEST(BitReaderTest, SkipBitsConditionalTest) { + uint8_t buffer[] = {0x8a, 0x12}; + BitReader reader(buffer, sizeof(buffer)); + EXPECT_TRUE(reader.SkipBitsConditional(false, 2)); + EXPECT_EQ(1, reader.bit_position()); // Not skipped. + EXPECT_TRUE(reader.SkipBitsConditional(false, 3)); + EXPECT_EQ(5, reader.bit_position()); // Skipped. + EXPECT_TRUE(reader.SkipBitsConditional(true, 2)); + EXPECT_EQ(6, reader.bit_position()); // Not skipped. + EXPECT_TRUE(reader.SkipBitsConditional(true, 5)); + EXPECT_EQ(12, reader.bit_position()); // Skipped. + EXPECT_TRUE(reader.SkipBits(4)); + EXPECT_FALSE(reader.SkipBits(1)); +} + } // namespace media } // namespace edash_packager diff --git a/packager/media/filters/filters.gyp b/packager/media/filters/filters.gyp index ff624a8d92..0c775555f3 100644 --- a/packager/media/filters/filters.gyp +++ b/packager/media/filters/filters.gyp @@ -25,8 +25,11 @@ 'h264_parser.h', 'vp_codec_configuration.cc', 'vp_codec_configuration.h', + 'vp8_parser.cc', + 'vp8_parser.h', 'vp9_parser.cc', 'vp9_parser.h', + 'vpx_parser.h', ], 'dependencies': [ '../../base/base.gyp:base', @@ -42,6 +45,7 @@ 'h264_parser_unittest.cc', 'hevc_decoder_configuration_unittest.cc', 'vp_codec_configuration_unittest.cc', + 'vp8_parser_unittest.cc', 'vp9_parser_unittest.cc', ], 'dependencies': [ diff --git a/packager/media/filters/vp8_parser.cc b/packager/media/filters/vp8_parser.cc new file mode 100644 index 0000000000..b5f8f59086 --- /dev/null +++ b/packager/media/filters/vp8_parser.cc @@ -0,0 +1,195 @@ +// Copyright 2015 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 or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/filters/vp8_parser.h" + +#include "packager/base/logging.h" +#include "packager/media/base/bit_reader.h" +#include "packager/media/formats/mp4/rcheck.h" + +namespace edash_packager { +namespace media { +namespace { + +const uint32_t MB_FEATURE_TREE_PROBS = 3; +const uint32_t MAX_MB_SEGMENTS = 4; +const uint32_t MAX_REF_LF_DELTAS = 4; +const uint32_t MAX_MODE_LF_DELTAS = 4; +const uint32_t MB_LVL_MAX = 2; +const uint32_t MB_FEATURE_DATA_BITS[MB_LVL_MAX] = {7, 6}; + +bool VerifySyncCode(const uint8_t* data) { + return data[0] == 0x9d && data[1] == 0x01 && data[2] == 0x2a; +} + +bool ReadSegmentation(BitReader* reader) { + bool enabled; + RCHECK(reader->ReadBits(1, &enabled)); + if (!enabled) + return true; + + bool update_map; + RCHECK(reader->ReadBits(1, &update_map)); + bool update_data; + RCHECK(reader->ReadBits(1, &update_data)); + + if (update_data) { + RCHECK(reader->SkipBits(1)); // abs_delta + for (uint32_t i = 0; i < MAX_MB_SEGMENTS; ++i) + for (uint32_t j = 0; j < MB_LVL_MAX; ++j) { + RCHECK(reader->SkipBitsConditional(true, MB_FEATURE_DATA_BITS[j] + 1)); + } + } + if (update_map) { + for (uint32_t i = 0; i < MB_FEATURE_TREE_PROBS; ++i) + RCHECK(reader->SkipBitsConditional(true, 8)); + } + return true; +} + +bool ReadLoopFilter(BitReader* reader) { + RCHECK(reader->SkipBits(10)); // filter_type, filter_evel, sharness_level + + bool mode_ref_delta_enabled; + RCHECK(reader->ReadBits(1, &mode_ref_delta_enabled)); + if (!mode_ref_delta_enabled) + return true; + bool mode_ref_delta_update; + RCHECK(reader->ReadBits(1, &mode_ref_delta_update)); + if (!mode_ref_delta_update) + return true; + + for (uint32_t i = 0; i < MAX_REF_LF_DELTAS + MAX_MODE_LF_DELTAS; ++i) + RCHECK(reader->SkipBitsConditional(true, 6 + 1)); + return true; +} + +bool ReadQuantization(BitReader* reader) { + uint32_t yac_index; + RCHECK(reader->ReadBits(7, &yac_index)); + VLOG(4) << "yac_index: " << yac_index; + RCHECK(reader->SkipBitsConditional(true, 4 + 1)); // y dc delta + RCHECK(reader->SkipBitsConditional(true, 4 + 1)); // y2 dc delta + RCHECK(reader->SkipBitsConditional(true, 4 + 1)); // y2 ac delta + RCHECK(reader->SkipBitsConditional(true, 4 + 1)); // chroma dc delta + RCHECK(reader->SkipBitsConditional(true, 4 + 1)); // chroma ac delta + return true; +} + +bool ReadRefreshFrame(BitReader* reader) { + bool refresh_golden_frame; + RCHECK(reader->ReadBits(1, &refresh_golden_frame)); + bool refresh_altref_frame; + RCHECK(reader->ReadBits(1, &refresh_altref_frame)); + if (!refresh_golden_frame) + RCHECK(reader->SkipBits(2)); // buffer copy flag + if (!refresh_altref_frame) + RCHECK(reader->SkipBits(2)); // buffer copy flag + RCHECK(reader->SkipBits(2)); // sign bias flags + return true; +} + +} // namespace + +VP8Parser::VP8Parser() : width_(0), height_(0) {} +VP8Parser::~VP8Parser() {} + +bool VP8Parser::Parse(const uint8_t* data, + size_t data_size, + std::vector* vpx_frames) { + DCHECK(data); + DCHECK(vpx_frames); + + BitReader reader(data, data_size); + // The following 3 bytes are read directly from |data|. + RCHECK(reader.SkipBytes(3)); + + // One bit for frame type. + bool is_interframe = data[0] & 1; + // 3-bit version number with 2 bits for profile and the other bit reserved for + // future variants. + uint8_t profile = (data[0] >> 1) & 3; + // One bit for show frame flag. + // Then 19 bits (the remaining 3 bits in the first byte + next two bytes) for + // header size. + uint32_t header_size = (data[0] | (data[1] << 8) | (data[2] << 16)) >> 5; + RCHECK(header_size <= data_size); + + if (!is_interframe) { + // The following 7 bytes are read directly from |data|. + RCHECK(reader.SkipBytes(7)); + + RCHECK(VerifySyncCode(&data[3])); + + // Bits 0b11000000 for data[7] and data[9] are scaling. + width_ = data[6] | ((data[7] & 0x3f) << 8); + height_ = data[8] | ((data[9] & 0x3f) << 8); + + RCHECK(reader.SkipBits(2)); // colorspace and pixel value clamping. + } + + RCHECK(ReadSegmentation(&reader)); + RCHECK(ReadLoopFilter(&reader)); + RCHECK(reader.SkipBits(2)); // partitions bits + RCHECK(ReadQuantization(&reader)); + + if (is_interframe) { + RCHECK(ReadRefreshFrame(&reader)); + RCHECK(reader.SkipBits(1)); // refresh_entropy_probs + RCHECK(reader.SkipBits(1)); // refresh last frame flag + } else { + RCHECK(reader.SkipBits(1)); // refresh_entropy_probs + } + + // The next field is entropy header (coef probability tree), which is encoded + // using bool entropy encoder, i.e. compressed. We don't consider it as part + // of uncompressed header. + + writable_codec_config()->set_profile(profile); + // VP8 uses an 8-bit YUV 4:2:0 format. + // http://tools.ietf.org/html/rfc6386 Section 2. + writable_codec_config()->set_bit_depth(8); + writable_codec_config()->set_chroma_subsampling( + VPCodecConfiguration::CHROMA_420_COLLOCATED_WITH_LUMA); + // VP8 uses YCrCb color space defined in ITU-R_BT.601. + // http://tools.ietf.org/html/rfc6386 Section 9.2. + writable_codec_config()->set_color_space( + VPCodecConfiguration::COLOR_SPACE_BT_601); + + VPxFrameInfo vpx_frame; + vpx_frame.frame_size = data_size; + vpx_frame.uncompressed_header_size = + vpx_frame.frame_size - reader.bits_available() / 8; + vpx_frame.is_keyframe = !is_interframe; + vpx_frame.width = width_; + vpx_frame.height = height_; + + vpx_frames->clear(); + vpx_frames->push_back(vpx_frame); + + VLOG(3) << "\n frame_size: " << vpx_frame.frame_size + << "\n uncompressed_header_size: " + << vpx_frame.uncompressed_header_size + << "\n bits read: " << reader.bit_position() + << "\n header_size: " << header_size + << "\n width: " << vpx_frame.width + << "\n height: " << vpx_frame.height; + return true; +} + +bool VP8Parser::IsKeyframe(const uint8_t* data, size_t data_size) { + // Make sure the block is big enough for the minimal keyframe header size. + if (data_size < 10) + return false; + + // The LSb of the first byte must be a 0 for a keyframe. + if ((data[0] & 0x01) != 0) + return false; + return VerifySyncCode(&data[3]); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/filters/vp8_parser.h b/packager/media/filters/vp8_parser.h new file mode 100644 index 0000000000..1c197fd0ba --- /dev/null +++ b/packager/media/filters/vp8_parser.h @@ -0,0 +1,55 @@ +// Copyright 2015 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 or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_FILTERS_VP8_PARSER_H_ +#define MEDIA_FILTERS_VP8_PARSER_H_ + +#include +#include + +#include "packager/base/macros.h" +#include "packager/media/filters/vpx_parser.h" + +namespace edash_packager { +namespace media { + +/// Class to parse a vp8 bit stream. Implemented according to +/// https://tools.ietf.org/html/rfc6386. +class VP8Parser : public VPxParser { + public: + VP8Parser(); + ~VP8Parser() override; + + /// Parse @a data with size @a data_size. + /// @param data_size Size of the sample in bytes. Note that it should be a + /// full sample. + /// @param[out] vpx_frames points to the list of VPx frames for the current + /// sample on success. Cannot be NULL. + /// @return true on success, false otherwise. + bool Parse(const uint8_t* data, + size_t data_size, + std::vector* vpx_frames) override; + + /// A convenient utility function to check whether the frame is a keyframe. + /// Note that this function does not do a full parse of the frame header, so + /// should be more efficient than Parse(). + /// @param data_size Size of the sample in bytes. + /// @return true if it is, false if it is not or if there is parsing error. + static bool IsKeyframe(const uint8_t* data, size_t data_size); + + private: + // Keep track of the current width and height. Note that they may change from + // frame to frame. + uint32_t width_; + uint32_t height_; + + DISALLOW_COPY_AND_ASSIGN(VP8Parser); +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FILTERS_VP8_PARSER_H_ diff --git a/packager/media/filters/vp8_parser_unittest.cc b/packager/media/filters/vp8_parser_unittest.cc new file mode 100644 index 0000000000..9ca7d47c1c --- /dev/null +++ b/packager/media/filters/vp8_parser_unittest.cc @@ -0,0 +1,105 @@ +// Copyright 2015 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 or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/filters/vp8_parser.h" + +#include +#include + +using ::testing::ElementsAre; + +namespace edash_packager { +namespace media { +namespace { +MATCHER_P5(EqualVPxFrame, + frame_size, + uncompressed_header_size, + is_keyframe, + width, + height, + "") { + *result_listener << "which is (" << arg.frame_size << ", " + << arg.uncompressed_header_size << ", " << arg.is_keyframe + << ", " << arg.width << ", " << arg.height << ")."; + return arg.frame_size == frame_size && + arg.uncompressed_header_size == uncompressed_header_size && + arg.is_keyframe == is_keyframe && arg.width == width && + arg.height == height; +} +} // namespace + +TEST(VP8ParserTest, Keyframe) { + const uint8_t kData[] = { + 0x54, 0x04, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47, + 0x08, 0x85, 0x85, 0x88, 0x85, 0x84, 0x88, 0x01, 0x24, 0x10, 0x17, 0x67, + 0x63, 0x3f, 0xbb, 0xe5, 0xcf, 0x9b, 0x7d, 0x53, 0xec, 0x67, 0xa2, 0xcf, + }; + + EXPECT_TRUE(VP8Parser::IsKeyframe(kData, arraysize(kData))); + + VP8Parser parser; + std::vector frames; + ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); + EXPECT_EQ("vp08.02.00.08.01.01.00.00", + parser.codec_config().GetCodecString(kCodecVP8)); + EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 22u, true, + 320u, 240u))); +} + +TEST(VP8ParserTest, NonKeyframe) { + const uint8_t kData[] = { + 0x31, 0x03, 0x00, 0x11, 0x10, 0xa4, 0x00, 0x1a, 0xea, 0xd8, 0xaf, 0x40, + 0xcf, 0x80, 0x2f, 0xdc, 0x9d, 0x42, 0x4b, 0x19, 0xc8, 0x04, 0x97, 0x28, + 0x34, 0x7b, 0x47, 0xfc, 0x2d, 0xaa, 0x0b, 0xbb, 0xc6, 0xc3, 0xc1, 0x12, + }; + + EXPECT_FALSE(VP8Parser::IsKeyframe(kData, arraysize(kData))); + + VP8Parser parser; + std::vector frames; + ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); + EXPECT_THAT(frames, + ElementsAre(EqualVPxFrame(arraysize(kData), 8u, false, 0u, 0u))); +} + +TEST(VP8ParserTest, InsufficientData) { + const uint8_t kData[] = {0x00, 0x0a}; + EXPECT_FALSE(VP8Parser::IsKeyframe(kData, arraysize(kData))); + VP8Parser parser; + std::vector frames; + ASSERT_FALSE(parser.Parse(kData, arraysize(kData), &frames)); +} + +TEST(VP8ParserTest, CorruptedSynccode) { + const uint8_t kData[] = { + 0x54, 0x04, 0x00, 0x9d, 0x21, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47, + 0x08, 0x85, 0x85, 0x88, 0x85, 0x84, 0x88, 0x01, 0x24, 0x10, 0x17, 0x67, + 0x63, 0x3f, 0xbb, 0xe5, 0xcf, 0x9b, 0x7d, 0x53, 0xec, 0x67, 0xa2, 0xcf, + }; + EXPECT_FALSE(VP8Parser::IsKeyframe(kData, arraysize(kData))); + VP8Parser parser; + std::vector frames; + ASSERT_FALSE(parser.Parse(kData, arraysize(kData), &frames)); +} + +TEST(VP8ParserTest, NotEnoughBytesForHeaderSize) { + const uint8_t kData[] = { + 0x54, 0x06, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47, + 0x08, 0x85, 0x85, 0x88, 0x85, 0x84, 0x88, 0x01, 0x24, 0x10, 0x17, 0x67, + 0x63, 0x3f, 0xbb, 0xe5, 0xcf, 0x9b, 0x7d, 0x53, 0xec, 0x67, 0xa2, 0xcf, + }; + + // IsKeyframe only parses the bytes that is necessary to determine whether it + // is a keyframe. + EXPECT_TRUE(VP8Parser::IsKeyframe(kData, arraysize(kData))); + + VP8Parser parser; + std::vector frames; + EXPECT_FALSE(parser.Parse(kData, arraysize(kData), &frames)); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/filters/vp9_parser.cc b/packager/media/filters/vp9_parser.cc index f04d70618a..d0298e6678 100644 --- a/packager/media/filters/vp9_parser.cc +++ b/packager/media/filters/vp9_parser.cc @@ -46,22 +46,6 @@ enum VpxColorSpace { VPX_COLOR_SPACE_SRGB = 7, }; -class VP9BitReader : public BitReader { - public: - VP9BitReader(const uint8_t* data, off_t size) : BitReader(data, size) {} - ~VP9BitReader() {} - - bool SkipBitsConditional(uint32_t num_bits) { - bool condition; - if (!ReadBits(1, &condition)) - return false; - return condition ? SkipBits(num_bits) : true; - } - - private: - DISALLOW_COPY_AND_ASSIGN(VP9BitReader); -}; - uint32_t RoundupShift(uint32_t value, uint32_t n) { return (value + (1 << n) - 1) >> n; } @@ -156,7 +140,7 @@ bool ParseIfSuperframeIndex(const uint8_t* data, return true; } -bool ReadProfile(VP9BitReader* reader, VPCodecConfiguration* codec_config) { +bool ReadProfile(BitReader* reader, VPCodecConfiguration* codec_config) { uint8_t bit[2]; RCHECK(reader->ReadBits(1, &bit[0])); RCHECK(reader->ReadBits(1, &bit[1])); @@ -170,7 +154,7 @@ bool ReadProfile(VP9BitReader* reader, VPCodecConfiguration* codec_config) { return true; } -bool ReadSyncCode(VP9BitReader* reader) { +bool ReadSyncCode(BitReader* reader) { uint32_t sync_code; RCHECK(reader->ReadBits(24, &sync_code)); return sync_code == VP9_SYNC_CODE; @@ -222,7 +206,7 @@ VPCodecConfiguration::ChromaSubsampling GetChromaSubsampling( } } -bool ReadBitDepthAndColorSpace(VP9BitReader* reader, +bool ReadBitDepthAndColorSpace(BitReader* reader, VPCodecConfiguration* codec_config) { uint8_t bit_depth = 8; if (codec_config->profile() >= 2) { @@ -284,7 +268,7 @@ bool ReadBitDepthAndColorSpace(VP9BitReader* reader, return true; } -bool ReadFrameSize(VP9BitReader* reader, uint32_t* width, uint32_t* height) { +bool ReadFrameSize(BitReader* reader, uint32_t* width, uint32_t* height) { RCHECK(reader->ReadBits(16, width)); *width += 1; // Off by 1. RCHECK(reader->ReadBits(16, height)); @@ -292,7 +276,7 @@ bool ReadFrameSize(VP9BitReader* reader, uint32_t* width, uint32_t* height) { return true; } -bool ReadDisplayFrameSize(VP9BitReader* reader, +bool ReadDisplayFrameSize(BitReader* reader, uint32_t* display_width, uint32_t* display_height) { bool has_display_size; @@ -302,7 +286,7 @@ bool ReadDisplayFrameSize(VP9BitReader* reader, return true; } -bool ReadFrameSizes(VP9BitReader* reader, uint32_t* width, uint32_t* height) { +bool ReadFrameSizes(BitReader* reader, uint32_t* width, uint32_t* height) { uint32_t new_width; uint32_t new_height; RCHECK(ReadFrameSize(reader, &new_width, &new_height)); @@ -321,7 +305,7 @@ bool ReadFrameSizes(VP9BitReader* reader, uint32_t* width, uint32_t* height) { return true; } -bool ReadFrameSizesWithRefs(VP9BitReader* reader, +bool ReadFrameSizesWithRefs(BitReader* reader, uint32_t* width, uint32_t* height) { bool found = false; @@ -340,7 +324,7 @@ bool ReadFrameSizesWithRefs(VP9BitReader* reader, return true; } -bool ReadLoopFilter(VP9BitReader* reader) { +bool ReadLoopFilter(BitReader* reader) { RCHECK(reader->SkipBits(9)); // filter_evel, sharness_level bool mode_ref_delta_enabled; RCHECK(reader->ReadBits(1, &mode_ref_delta_enabled)); @@ -348,22 +332,23 @@ bool ReadLoopFilter(VP9BitReader* reader) { return true; bool mode_ref_delta_update; RCHECK(reader->ReadBits(1, &mode_ref_delta_update)); - if (!mode_ref_delta_update) return true; + if (!mode_ref_delta_update) + return true; for (uint32_t i = 0; i < MAX_REF_LF_DELTAS + MAX_MODE_LF_DELTAS; ++i) - RCHECK(reader->SkipBitsConditional(6 + 1)); + RCHECK(reader->SkipBitsConditional(true, 6 + 1)); return true; } -bool ReadQuantization(VP9BitReader* reader) { +bool ReadQuantization(BitReader* reader) { RCHECK(reader->SkipBits(QINDEX_BITS)); // Skip delta_q bits. for (uint32_t i = 0; i < 3; ++i) - RCHECK(reader->SkipBitsConditional(4 + 1)); + RCHECK(reader->SkipBitsConditional(true, 4 + 1)); return true; } -bool ReadSegmentation(VP9BitReader* reader) { +bool ReadSegmentation(BitReader* reader) { bool enabled; RCHECK(reader->ReadBits(1, &enabled)); if (!enabled) @@ -373,13 +358,13 @@ bool ReadSegmentation(VP9BitReader* reader) { RCHECK(reader->ReadBits(1, &update_map)); if (update_map) { for (uint32_t i = 0; i < SEG_TREE_PROBS; ++i) - RCHECK(reader->SkipBitsConditional(8)); + RCHECK(reader->SkipBitsConditional(true, 8)); bool temporal_update; RCHECK(reader->ReadBits(1, &temporal_update)); if (temporal_update) { for (uint32_t j = 0; j < PREDICTION_PROBS; ++j) - RCHECK(reader->SkipBitsConditional(8)); + RCHECK(reader->SkipBitsConditional(true, 8)); } } @@ -402,7 +387,7 @@ bool ReadSegmentation(VP9BitReader* reader) { return true; } -bool ReadTileInfo(uint32_t width, VP9BitReader* reader) { +bool ReadTileInfo(uint32_t width, BitReader* reader) { uint32_t mi_cols = GetNumMiUnits(width); uint32_t min_log2_tile_cols; @@ -420,7 +405,7 @@ bool ReadTileInfo(uint32_t width, VP9BitReader* reader) { } RCHECK(log2_tile_cols <= 6); - RCHECK(reader->SkipBitsConditional(1)); // log2_tile_rows + RCHECK(reader->SkipBitsConditional(true, 1)); // log2_tile_rows return true; } @@ -438,12 +423,12 @@ bool VP9Parser::Parse(const uint8_t* data, for (auto& vpx_frame : *vpx_frames) { VLOG(4) << "process frame with size " << vpx_frame.frame_size; - VP9BitReader reader(data, vpx_frame.frame_size); + BitReader reader(data, vpx_frame.frame_size); uint8_t frame_marker; RCHECK(reader.ReadBits(2, &frame_marker)); RCHECK(frame_marker == VP9_FRAME_MARKER); - RCHECK(ReadProfile(&reader, &codec_config_)); + RCHECK(ReadProfile(&reader, writable_codec_config())); bool show_existing_frame; RCHECK(reader.ReadBits(1, &show_existing_frame)); @@ -470,7 +455,7 @@ bool VP9Parser::Parse(const uint8_t* data, if (vpx_frame.is_keyframe) { RCHECK(ReadSyncCode(&reader)); - RCHECK(ReadBitDepthAndColorSpace(&reader, &codec_config_)); + RCHECK(ReadBitDepthAndColorSpace(&reader, writable_codec_config())); RCHECK(ReadFrameSizes(&reader, &width_, &height_)); } else { bool intra_only = false; @@ -481,16 +466,16 @@ bool VP9Parser::Parse(const uint8_t* data, if (intra_only) { RCHECK(ReadSyncCode(&reader)); - if (codec_config_.profile() > 0) { - RCHECK(ReadBitDepthAndColorSpace(&reader, &codec_config_)); + if (codec_config().profile() > 0) { + RCHECK(ReadBitDepthAndColorSpace(&reader, writable_codec_config())); } else { // NOTE: The intra-only frame header does not include the // specification of either the color format or color sub-sampling in // profile 0. VP9 specifies that the default color format should be // YUV 4:2:0 in this case (normative). - codec_config_.set_chroma_subsampling( + writable_codec_config()->set_chroma_subsampling( VPCodecConfiguration::CHROMA_420_COLLOCATED_WITH_LUMA); - codec_config_.set_bit_depth(8); + writable_codec_config()->set_bit_depth(8); } RCHECK(reader.SkipBits(REF_FRAMES)); // refresh_frame_flags @@ -519,26 +504,27 @@ bool VP9Parser::Parse(const uint8_t* data, } RCHECK(reader.SkipBits(FRAME_CONTEXTS_LOG2)); // frame_context_idx - VLOG(4) << "Bits read before ReadLoopFilter: " << reader.bit_position(); + VLOG(4) << "bits read before ReadLoopFilter: " << reader.bit_position(); RCHECK(ReadLoopFilter(&reader)); RCHECK(ReadQuantization(&reader)); RCHECK(ReadSegmentation(&reader)); RCHECK(ReadTileInfo(width_, &reader)); - uint16_t first_partition_size; - RCHECK(reader.ReadBits(16, &first_partition_size)); + uint16_t header_size; + RCHECK(reader.ReadBits(16, &header_size)); vpx_frame.uncompressed_header_size = vpx_frame.frame_size - reader.bits_available() / 8; vpx_frame.width = width_; vpx_frame.height = height_; VLOG(3) << "\n frame_size: " << vpx_frame.frame_size - << "\n header_size: " << vpx_frame.uncompressed_header_size - << "\n Bits read: " << reader.bit_position() - << "\n first_partition_size: " << first_partition_size; + << "\n uncompressed_header_size: " + << vpx_frame.uncompressed_header_size + << "\n bits read: " << reader.bit_position() + << "\n header_size: " << header_size; - RCHECK(first_partition_size > 0); - RCHECK(first_partition_size * 8 <= reader.bits_available()); + RCHECK(header_size > 0); + RCHECK(header_size * 8 <= reader.bits_available()); data += vpx_frame.frame_size; } @@ -546,7 +532,7 @@ bool VP9Parser::Parse(const uint8_t* data, } bool VP9Parser::IsKeyframe(const uint8_t* data, size_t data_size) { - VP9BitReader reader(data, data_size); + BitReader reader(data, data_size); uint8_t frame_marker; RCHECK(reader.ReadBits(2, &frame_marker)); RCHECK(frame_marker == VP9_FRAME_MARKER); diff --git a/packager/media/filters/vp9_parser.h b/packager/media/filters/vp9_parser.h index effd7769b2..e2135b3339 100644 --- a/packager/media/filters/vp9_parser.h +++ b/packager/media/filters/vp9_parser.h @@ -11,24 +11,16 @@ #include #include "packager/base/macros.h" -#include "packager/media/filters/vp_codec_configuration.h" +#include "packager/media/filters/vpx_parser.h" namespace edash_packager { namespace media { -struct VPxFrameInfo { - size_t frame_size; - size_t uncompressed_header_size; - bool is_keyframe; - uint32_t width; - uint32_t height; -}; - /// Class to parse a vp9 bit stream. -class VP9Parser { +class VP9Parser : public VPxParser { public: VP9Parser(); - ~VP9Parser(); + ~VP9Parser() override; /// Parse @a data with size @a data_size. /// @param data_size Size of the sample in bytes. Note that it should be a @@ -38,11 +30,7 @@ class VP9Parser { /// @return true on success, false otherwise. bool Parse(const uint8_t* data, size_t data_size, - std::vector* vpx_frames); - - /// @return VPx codec configuration extracted. Note that it is only valid - /// after parsing a keyframe or intra frame successfully. - const VPCodecConfiguration& codec_config() { return codec_config_; } + std::vector* vpx_frames) override; /// A convenient utility function to check whether the frame is a keyframe. /// Note that this function does not do a full parse of the frame header, so @@ -57,8 +45,6 @@ class VP9Parser { uint32_t width_; uint32_t height_; - VPCodecConfiguration codec_config_; - DISALLOW_COPY_AND_ASSIGN(VP9Parser); }; diff --git a/packager/media/filters/vp9_parser_unittest.cc b/packager/media/filters/vp9_parser_unittest.cc index ae1607391e..0c2ec43119 100644 --- a/packager/media/filters/vp9_parser_unittest.cc +++ b/packager/media/filters/vp9_parser_unittest.cc @@ -226,7 +226,7 @@ TEST(VP9ParserTest, CorruptedSynccode) { ASSERT_FALSE(parser.Parse(kData, arraysize(kData), &frames)); } -TEST(VP9ParserTest, NotEnoughBytesForFirstPartitionSize) { +TEST(VP9ParserTest, NotEnoughBytesForHeaderSize) { const uint8_t kData[] = { 0x82, 0x49, 0x83, 0x42, 0x04, 0xaf, 0xf0, 0x06, 0xbb, 0xdd, 0xf8, 0x03, 0xfc, 0x00, 0x38, 0x24, 0x1c, 0x18, 0x00, 0x00, 0x03, 0x38, 0x7f, 0x8f, diff --git a/packager/media/filters/vpx_parser.h b/packager/media/filters/vpx_parser.h new file mode 100644 index 0000000000..2fdcba20b0 --- /dev/null +++ b/packager/media/filters/vpx_parser.h @@ -0,0 +1,58 @@ +// Copyright 2015 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 or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_FILTERS_VPX_PARSER_H_ +#define MEDIA_FILTERS_VPX_PARSER_H_ + +#include +#include + +#include "packager/base/macros.h" +#include "packager/media/filters/vp_codec_configuration.h" + +namespace edash_packager { +namespace media { + +struct VPxFrameInfo { + size_t frame_size; + size_t uncompressed_header_size; + bool is_keyframe; + uint32_t width; + uint32_t height; +}; + +class VPxParser { + public: + VPxParser() {} + virtual ~VPxParser() {} + + /// Parse @a data with size @a data_size. + /// @param data_size Size of the sample in bytes. Note that it should be a + /// full sample. + /// @param[out] vpx_frames points to the list of VPx frames for the current + /// sample on success. Cannot be NULL. + /// @return true on success, false otherwise. + virtual bool Parse(const uint8_t* data, + size_t data_size, + std::vector* vpx_frames) = 0; + + /// @return VPx codec configuration extracted. Note that it is only valid + /// after parsing a keyframe or intra frame successfully. + const VPCodecConfiguration& codec_config() const { return codec_config_; } + + protected: + VPCodecConfiguration* writable_codec_config() { return &codec_config_; } + + private: + VPCodecConfiguration codec_config_; + + DISALLOW_COPY_AND_ASSIGN(VPxParser); +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FILTERS_VPX_PARSER_H_ diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc index 8113c89a7f..f5036e053d 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ b/packager/media/formats/mp4/encrypting_fragmenter.cc @@ -10,6 +10,7 @@ #include "packager/media/base/buffer_reader.h" #include "packager/media/base/key_source.h" #include "packager/media/base/media_sample.h" +#include "packager/media/filters/vp8_parser.h" #include "packager/media/filters/vp9_parser.h" #include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/cenc.h" @@ -35,8 +36,11 @@ EncryptingFragmenter::EncryptingFragmenter( nalu_length_size_(nalu_length_size), clear_time_(clear_time) { DCHECK(encryption_key_); - if (video_codec == kCodecVP9) - vp9_parser_.reset(new VP9Parser); + if (video_codec == kCodecVP8) { + vpx_parser_.reset(new VP8Parser); + } else if (video_codec == kCodecVP9) { + vpx_parser_.reset(new VP9Parser); + } } EncryptingFragmenter::~EncryptingFragmenter() {} @@ -140,11 +144,11 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr sample) { FrameCENCInfo cenc_info(encryptor_->iv()); uint8_t* data = sample->writable_data(); if (IsSubsampleEncryptionRequired()) { - if (video_codec_ == kCodecVP9) { + if (vpx_parser_) { std::vector vpx_frames; - if (!vp9_parser_->Parse(sample->data(), sample->data_size(), + if (!vpx_parser_->Parse(sample->data(), sample->data_size(), &vpx_frames)) { - return Status(error::MUXER_FAILURE, "Failed to parse vp9 frame."); + return Status(error::MUXER_FAILURE, "Failed to parse vpx frame."); } for (const VPxFrameInfo& frame : vpx_frames) { SubsampleEntry subsample; diff --git a/packager/media/formats/mp4/encrypting_fragmenter.h b/packager/media/formats/mp4/encrypting_fragmenter.h index 75b4531e81..8692000976 100644 --- a/packager/media/formats/mp4/encrypting_fragmenter.h +++ b/packager/media/formats/mp4/encrypting_fragmenter.h @@ -8,7 +8,7 @@ #define MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_ #include "packager/base/memory/scoped_ptr.h" -#include "packager/media/filters/vp9_parser.h" +#include "packager/media/filters/vpx_parser.h" #include "packager/media/formats/mp4/fragmenter.h" namespace edash_packager { @@ -71,7 +71,7 @@ class EncryptingFragmenter : public Fragmenter { // Should we enable subsample encryption? bool IsSubsampleEncryptionRequired() { - return video_codec_ == kCodecVP9 || nalu_length_size_ != 0; + return vpx_parser_ || nalu_length_size_ != 0; } scoped_ptr encryption_key_; @@ -85,7 +85,7 @@ class EncryptingFragmenter : public Fragmenter { const uint8_t nalu_length_size_; int64_t clear_time_; - scoped_ptr vp9_parser_; + scoped_ptr vpx_parser_; DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter); }; diff --git a/packager/media/formats/webm/webm_cluster_parser.cc b/packager/media/formats/webm/webm_cluster_parser.cc index ba074432f9..f5cfc1be9a 100644 --- a/packager/media/formats/webm/webm_cluster_parser.cc +++ b/packager/media/formats/webm/webm_cluster_parser.cc @@ -10,6 +10,7 @@ #include "packager/base/sys_byteorder.h" #include "packager/media/base/decrypt_config.h" #include "packager/media/base/timestamp.h" +#include "packager/media/filters/vp8_parser.h" #include "packager/media/filters/vp9_parser.h" #include "packager/media/filters/webvtt_util.h" #include "packager/media/formats/webm/webm_constants.h" @@ -59,26 +60,15 @@ bool IsKeyframe(bool is_video, if (!is_video) return true; - if (codec == kCodecVP9) - return VP9Parser::IsKeyframe(data, size); - - CHECK_EQ(kCodecVP8, codec); - - // Make sure the block is big enough for the minimal keyframe header size. - if (size < 7) - return false; - - // The LSb of the first byte must be a 0 for a keyframe. - // http://tools.ietf.org/html/rfc6386 Section 19.1 - if ((data[0] & 0x01) != 0) - return false; - - // Verify VP8 keyframe startcode. - // http://tools.ietf.org/html/rfc6386 Section 19.1 - if (data[3] != 0x9d || data[4] != 0x01 || data[5] != 0x2a) - return false; - - return true; + switch (codec) { + case kCodecVP8: + return VP8Parser::IsKeyframe(data, size); + case kCodecVP9: + return VP9Parser::IsKeyframe(data, size); + default: + NOTIMPLEMENTED() << "Unsupported codec " << codec; + return false; + } } } // namespace @@ -583,33 +573,41 @@ bool WebMClusterParser::OnBlock(bool is_simple_block, buffer->set_duration(track->default_duration()); } - if (!initialized_) { + if (!init_cb_.is_null() && !initialized_) { std::vector> streams; if (audio_stream_info_) streams.push_back(audio_stream_info_); if (video_stream_info_) { if (stream_type == kStreamVideo) { - VPCodecConfiguration codec_config; - if (video_stream_info_->codec() == kCodecVP9) { - VP9Parser vp9_parser; - std::vector vpx_frames; - if (!vp9_parser.Parse(buffer->data(), buffer->data_size(), - &vpx_frames)) { - LOG(ERROR) << "Failed to parse vp9 frame."; + scoped_ptr vpx_parser; + switch (video_stream_info_->codec()) { + case kCodecVP8: + vpx_parser.reset(new VP8Parser); + break; + case kCodecVP9: + vpx_parser.reset(new VP9Parser); + break; + default: + NOTIMPLEMENTED() << "Unsupported codec " + << video_stream_info_->codec(); return false; - } - if (vpx_frames.size() != 1u || !vpx_frames[0].is_keyframe) { - LOG(ERROR) << "The first frame should be a key frame."; - return false; - } - codec_config = vp9_parser.codec_config(); } - // TODO(kqyang): Support VP8. + std::vector vpx_frames; + if (!vpx_parser->Parse(buffer->data(), buffer->data_size(), + &vpx_frames)) { + LOG(ERROR) << "Failed to parse vpx frame."; + return false; + } + if (vpx_frames.size() != 1u || !vpx_frames[0].is_keyframe) { + LOG(ERROR) << "The first frame should be a key frame."; + return false; + } + const VPCodecConfiguration* codec_config = &vpx_parser->codec_config(); video_stream_info_->set_codec_string( - codec_config.GetCodecString(video_stream_info_->codec())); + codec_config->GetCodecString(video_stream_info_->codec())); std::vector extra_data; - codec_config.Write(&extra_data); + codec_config->Write(&extra_data); video_stream_info_->set_extra_data(extra_data); streams.push_back(video_stream_info_); init_cb_.Run(streams); diff --git a/packager/media/formats/webm/webm_cluster_parser_unittest.cc b/packager/media/formats/webm/webm_cluster_parser_unittest.cc index 6f33b4716f..dd4d63dd48 100644 --- a/packager/media/formats/webm/webm_cluster_parser_unittest.cc +++ b/packager/media/formats/webm/webm_cluster_parser_unittest.cc @@ -139,6 +139,11 @@ const uint8_t kEncryptedFrame[] = { 0x01, }; +const uint8_t kVP8Frame[] = { + 0x52, 0x04, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x47, + 0x08, 0x85, 0x85, 0x88, 0x85, 0x84, 0x88, 0x01, 0x24, 0x10, 0x17, 0x67, + 0x63, 0x3f, 0xbb, 0xe5, 0xcf, 0x9b, 0x7d, 0x53, 0xec, 0x67, 0xa2, 0xcf, +}; const uint8_t kVP9Frame[] = { 0xb1, 0x24, 0xc1, 0xa1, 0x40, 0x00, 0x4f, 0x80, 0x2c, 0xa0, 0x41, 0xc1, 0x20, 0xe0, 0xc3, 0xf0, 0x00, 0x09, 0x00, 0x7c, 0x57, 0x77, 0x3f, 0x67, @@ -166,7 +171,7 @@ scoped_ptr CreateCluster(int timecode, data_length = block_info[i].data_length; } else { data = kDefaultBlockData; - data_length = sizeof(kDefaultBlockData); + data_length = arraysize(kDefaultBlockData); } if (block_info[i].use_simple_block) { @@ -190,23 +195,11 @@ scoped_ptr CreateCluster(int timecode, return cb.Finish(); } -// Creates a Cluster with one encrypted Block. |bytes_to_write| is number of -// bytes of the encrypted frame to write. -scoped_ptr CreateEncryptedCluster(int bytes_to_write) { - CHECK_GT(bytes_to_write, 0); - CHECK_LE(bytes_to_write, static_cast(sizeof(kEncryptedFrame))); - +// Creates a Cluster with one block. +scoped_ptr CreateCluster(const uint8_t* data, size_t data_size) { ClusterBuilder cb; cb.SetClusterTimecode(0); - cb.AddSimpleBlock(kVideoTrackNum, 0, 0, kEncryptedFrame, bytes_to_write); - return cb.Finish(); -} - -// Creates a Cluster with one vp9 frame (keyframe). -scoped_ptr CreateVP9Cluster() { - ClusterBuilder cb; - cb.SetClusterTimecode(0); - cb.AddSimpleBlock(kVideoTrackNum, 0, 0, kVP9Frame, arraysize(kVP9Frame)); + cb.AddSimpleBlock(kVideoTrackNum, 0, 0, data, data_size); return cb.Finish(); } @@ -378,7 +371,8 @@ class WebMClusterParserTest : public testing::Test { const std::string& audio_encryption_key_id, const std::string& video_encryption_key_id, const AudioCodec audio_codec, - const VideoCodec video_codec) { + const VideoCodec video_codec, + const MediaParser::InitCB& init_cb) { audio_stream_info_->set_codec(audio_codec); video_stream_info_->set_codec(video_codec); return new WebMClusterParser( @@ -387,14 +381,15 @@ class WebMClusterParserTest : public testing::Test { ignored_tracks, audio_encryption_key_id, video_encryption_key_id, base::Bind(&WebMClusterParserTest::NewSampleEvent, base::Unretained(this)), - base::Bind(&WebMClusterParserTest::InitEvent, base::Unretained(this))); + init_cb); } // Create a default version of the parser for test. WebMClusterParser* CreateDefaultParser() { return CreateParserHelper(kNoTimestamp, kNoTimestamp, TextTracks(), std::set(), std::string(), std::string(), - kUnknownAudioCodec, kCodecVP8); + kUnknownAudioCodec, kCodecVP8, + MediaParser::InitCB()); } // Create a parser for test with custom audio and video default durations, and @@ -405,15 +400,16 @@ class WebMClusterParserTest : public testing::Test { const WebMTracksParser::TextTracks& text_tracks = TextTracks()) { return CreateParserHelper(audio_default_duration, video_default_duration, text_tracks, std::set(), std::string(), - std::string(), kUnknownAudioCodec, kCodecVP8); + std::string(), kUnknownAudioCodec, kCodecVP8, + MediaParser::InitCB()); } // Create a parser for test with custom ignored tracks. WebMClusterParser* CreateParserWithIgnoredTracks( std::set& ignored_tracks) { - return CreateParserHelper(kNoTimestamp, kNoTimestamp, TextTracks(), - ignored_tracks, std::string(), std::string(), - kUnknownAudioCodec, kCodecVP8); + return CreateParserHelper( + kNoTimestamp, kNoTimestamp, TextTracks(), ignored_tracks, std::string(), + std::string(), kUnknownAudioCodec, kCodecVP8, MediaParser::InitCB()); } // Create a parser for test with custom encryption key ids and audio codec. @@ -423,14 +419,17 @@ class WebMClusterParserTest : public testing::Test { const AudioCodec audio_codec) { return CreateParserHelper(kNoTimestamp, kNoTimestamp, TextTracks(), std::set(), audio_encryption_key_id, - video_encryption_key_id, audio_codec, kCodecVP8); + video_encryption_key_id, audio_codec, kCodecVP8, + MediaParser::InitCB()); } - // Create a parser for test with custom video codec. + // Create a parser for test with custom video codec, also check for init + // events. WebMClusterParser* CreateParserWithVideoCodec(const VideoCodec video_codec) { - return CreateParserHelper(kNoTimestamp, kNoTimestamp, TextTracks(), - std::set(), std::string(), std::string(), - kUnknownAudioCodec, video_codec); + return CreateParserHelper( + kNoTimestamp, kNoTimestamp, TextTracks(), std::set(), + std::string(), std::string(), kUnknownAudioCodec, video_codec, + base::Bind(&WebMClusterParserTest::InitEvent, base::Unretained(this))); } bool VerifyBuffers(const BlockInfo* block_info, int block_count) { @@ -563,10 +562,6 @@ TEST_F(WebMClusterParserTest, ParseClusterWithSingleCall) { int result = parser_->Parse(cluster->data(), cluster->size()); EXPECT_EQ(cluster->size(), result); ASSERT_TRUE(VerifyBuffers(kDefaultBlockInfo, block_count)); - // Verify init event called. - ASSERT_EQ(2u, streams_from_init_event_.size()); - EXPECT_EQ(kStreamAudio, streams_from_init_event_[0]->stream_type()); - EXPECT_EQ(kStreamVideo, streams_from_init_event_[1]->stream_type()); } TEST_F(WebMClusterParserTest, ParseClusterWithMultipleCalls) { @@ -626,7 +621,7 @@ TEST_F(WebMClusterParserTest, ParseBlockGroup) { 0xA1, 0x85, 0x82, 0x00, 0x21, 0x00, 0x55, // Block(size=5, track=2, ts=33) 0x9B, 0x81, 0x22, // BlockDuration(size=1, value=34) }; - const int kClusterSize = sizeof(kClusterData); + const int kClusterSize = arraysize(kClusterData); int result = parser_->Parse(kClusterData, kClusterSize); EXPECT_EQ(kClusterSize, result); @@ -780,8 +775,21 @@ TEST_F(WebMClusterParserTest, ParseMultipleTextTracks) { } } +TEST_F(WebMClusterParserTest, ParseVP8) { + scoped_ptr cluster(CreateCluster(kVP8Frame, arraysize(kVP8Frame))); + parser_.reset(CreateParserWithVideoCodec(kCodecVP8)); + + EXPECT_EQ(cluster->size(), parser_->Parse(cluster->data(), cluster->size())); + + ASSERT_EQ(2u, streams_from_init_event_.size()); + EXPECT_EQ(kStreamAudio, streams_from_init_event_[0]->stream_type()); + EXPECT_EQ(kStreamVideo, streams_from_init_event_[1]->stream_type()); + EXPECT_EQ("vp08.01.00.08.01.01.00.00", + streams_from_init_event_[1]->codec_string()); +} + TEST_F(WebMClusterParserTest, ParseVP9) { - scoped_ptr cluster(CreateVP9Cluster()); + scoped_ptr cluster(CreateCluster(kVP9Frame, arraysize(kVP9Frame))); parser_.reset(CreateParserWithVideoCodec(kCodecVP9)); EXPECT_EQ(cluster->size(), parser_->Parse(cluster->data(), cluster->size())); @@ -794,7 +802,8 @@ TEST_F(WebMClusterParserTest, ParseVP9) { } TEST_F(WebMClusterParserTest, ParseEncryptedBlock) { - scoped_ptr cluster(CreateEncryptedCluster(sizeof(kEncryptedFrame))); + scoped_ptr cluster( + CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame))); parser_.reset(CreateParserWithKeyIdsAndAudioCodec( std::string(), "video_key_id", kUnknownAudioCodec)); @@ -809,7 +818,7 @@ TEST_F(WebMClusterParserTest, ParseEncryptedBlock) { TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) { scoped_ptr cluster( - CreateEncryptedCluster(sizeof(kEncryptedFrame) - 2)); + CreateCluster(kEncryptedFrame, arraysize(kEncryptedFrame) - 2)); parser_.reset(CreateParserWithKeyIdsAndAudioCodec( std::string(), "video_key_id", kUnknownAudioCodec)); @@ -822,7 +831,7 @@ TEST_F(WebMClusterParserTest, ParseInvalidZeroSizedCluster) { 0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0) }; - EXPECT_EQ(-1, parser_->Parse(kBuffer, sizeof(kBuffer))); + EXPECT_EQ(-1, parser_->Parse(kBuffer, arraysize(kBuffer))); // Verify init event not called. ASSERT_EQ(0u, streams_from_init_event_.size()); } @@ -833,7 +842,7 @@ TEST_F(WebMClusterParserTest, ParseInvalidUnknownButActuallyZeroSizedCluster) { 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5) }; - EXPECT_EQ(-1, parser_->Parse(kBuffer, sizeof(kBuffer))); + EXPECT_EQ(-1, parser_->Parse(kBuffer, arraysize(kBuffer))); } TEST_F(WebMClusterParserTest, ParseInvalidTextBlockGroupWithoutDuration) {