Implement vp8 bit stream parser.
Change-Id: I8b7997ceb8e7484399442f7c1072f4d5aa33ff4e
This commit is contained in:
parent
94401d750a
commit
814d2414e3
|
@ -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);
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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': [
|
||||
|
|
|
@ -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<VPxFrameInfo>* 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
|
|
@ -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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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<VPxFrameInfo>* 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_
|
|
@ -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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
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<VPxFrameInfo> 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<VPxFrameInfo> 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<VPxFrameInfo> 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<VPxFrameInfo> 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<VPxFrameInfo> frames;
|
||||
EXPECT_FALSE(parser.Parse(kData, arraysize(kData), &frames));
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
|
@ -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);
|
||||
|
|
|
@ -11,24 +11,16 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#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<VPxFrameInfo>* 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<VPxFrameInfo>* 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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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<VPxFrameInfo>* 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_
|
|
@ -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<MediaSample> sample) {
|
|||
FrameCENCInfo cenc_info(encryptor_->iv());
|
||||
uint8_t* data = sample->writable_data();
|
||||
if (IsSubsampleEncryptionRequired()) {
|
||||
if (video_codec_ == kCodecVP9) {
|
||||
if (vpx_parser_) {
|
||||
std::vector<VPxFrameInfo> 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;
|
||||
|
|
|
@ -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<EncryptionKey> encryption_key_;
|
||||
|
@ -85,7 +85,7 @@ class EncryptingFragmenter : public Fragmenter {
|
|||
const uint8_t nalu_length_size_;
|
||||
int64_t clear_time_;
|
||||
|
||||
scoped_ptr<VP9Parser> vp9_parser_;
|
||||
scoped_ptr<VPxParser> vpx_parser_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter);
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
switch (codec) {
|
||||
case kCodecVP8:
|
||||
return VP8Parser::IsKeyframe(data, size);
|
||||
case 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)
|
||||
default:
|
||||
NOTIMPLEMENTED() << "Unsupported codec " << codec;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
} // 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<scoped_refptr<StreamInfo>> 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;
|
||||
scoped_ptr<VPxParser> 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;
|
||||
}
|
||||
std::vector<VPxFrameInfo> vpx_frames;
|
||||
if (!vp9_parser.Parse(buffer->data(), buffer->data_size(),
|
||||
if (!vpx_parser->Parse(buffer->data(), buffer->data_size(),
|
||||
&vpx_frames)) {
|
||||
LOG(ERROR) << "Failed to parse vp9 frame.";
|
||||
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;
|
||||
}
|
||||
codec_config = vp9_parser.codec_config();
|
||||
}
|
||||
// TODO(kqyang): Support VP8.
|
||||
|
||||
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<uint8_t> 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);
|
||||
|
|
|
@ -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<Cluster> 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<Cluster> 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<Cluster> CreateEncryptedCluster(int bytes_to_write) {
|
||||
CHECK_GT(bytes_to_write, 0);
|
||||
CHECK_LE(bytes_to_write, static_cast<int>(sizeof(kEncryptedFrame)));
|
||||
|
||||
// Creates a Cluster with one block.
|
||||
scoped_ptr<Cluster> 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<Cluster> 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<int64_t>(), 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<int64_t>(), 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<int64_t>& 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<int64_t>(), 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<int64_t>(), std::string(), std::string(),
|
||||
kUnknownAudioCodec, video_codec);
|
||||
return CreateParserHelper(
|
||||
kNoTimestamp, kNoTimestamp, TextTracks(), std::set<int64_t>(),
|
||||
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> 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> cluster(CreateVP9Cluster());
|
||||
scoped_ptr<Cluster> 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> cluster(CreateEncryptedCluster(sizeof(kEncryptedFrame)));
|
||||
scoped_ptr<Cluster> 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> 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) {
|
||||
|
|
Loading…
Reference in New Issue