Add file to parse data from VP9 frames.
This class will parse and collect some data from VP9 frame headers. BUG=https://bugs.chromium.org/p/webm/issues/detail?id=1188 Change-Id: Ie19e0f4a2f7e89d0378a66ce2e8e939250ddc383
This commit is contained in:
parent
a004fefc56
commit
8bb68c2b3e
@ -151,6 +151,14 @@ if (ENABLE_TESTS)
|
||||
"${LIBWEBM_SRC_DIR}/testing/test_util.h")
|
||||
target_link_libraries(parser_tests LINK_PUBLIC gtest webm)
|
||||
|
||||
add_executable(vp9_header_parser_tests
|
||||
"${LIBWEBM_SRC_DIR}/common/vp9_header_parser_tests.cc"
|
||||
"${LIBWEBM_SRC_DIR}/common/vp9_header_parser.cc"
|
||||
"${LIBWEBM_SRC_DIR}/common/vp9_header_parser.h"
|
||||
"${LIBWEBM_SRC_DIR}/testing/test_util.cc"
|
||||
"${LIBWEBM_SRC_DIR}/testing/test_util.h")
|
||||
target_link_libraries(vp9_header_parser_tests LINK_PUBLIC gtest webm)
|
||||
|
||||
if (ENABLE_WEBMTS)
|
||||
add_executable(webm2pes_tests
|
||||
"${LIBWEBM_SRC_DIR}/testing/test_util.cc"
|
||||
|
262
common/vp9_header_parser.cc
Normal file
262
common/vp9_header_parser.cc
Normal file
@ -0,0 +1,262 @@
|
||||
// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
#include "common/vp9_header_parser.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
namespace vp9_parser {
|
||||
|
||||
bool Vp9HeaderParser::SetFrame(const uint8_t* frame, size_t length) {
|
||||
if (!frame || length == 0)
|
||||
return false;
|
||||
|
||||
frame_ = frame;
|
||||
frame_size_ = length;
|
||||
bit_offset_ = 0;
|
||||
profile_ = -1;
|
||||
show_existing_frame_ = 0;
|
||||
key_ = 0;
|
||||
altref_ = 0;
|
||||
error_resilient_mode_ = 0;
|
||||
intra_only_ = 0;
|
||||
reset_frame_context_ = 0;
|
||||
color_space_ = 0;
|
||||
color_range_ = 0;
|
||||
subsampling_x_ = 0;
|
||||
subsampling_y_ = 0;
|
||||
refresh_frame_flags_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Vp9HeaderParser::ParseUncompressedHeader() {
|
||||
const int frame_marker = VpxReadLiteral(2);
|
||||
if (frame_marker != kVp9FrameMarker) {
|
||||
fprintf(stderr, "Invalid VP9 frame_marker:%d\n", frame_marker);
|
||||
return false;
|
||||
}
|
||||
|
||||
profile_ = VpxReadLiteral(2);
|
||||
if (profile_ > 2)
|
||||
profile_ += ReadBit();
|
||||
|
||||
// TODO(fgalligan): Decide how to handle show existing frames.
|
||||
show_existing_frame_ = ReadBit();
|
||||
if (show_existing_frame_)
|
||||
return true;
|
||||
|
||||
key_ = !ReadBit();
|
||||
altref_ = !ReadBit();
|
||||
error_resilient_mode_ = ReadBit();
|
||||
if (key_) {
|
||||
if (!ValidateVp9SyncCode()) {
|
||||
fprintf(stderr, "Invalid Sync code!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ParseColorSpace();
|
||||
ParseFrameResolution();
|
||||
ParseFrameParallelMode();
|
||||
ParseTileInfo();
|
||||
} else {
|
||||
intra_only_ = altref_ ? ReadBit() : 0;
|
||||
reset_frame_context_ = error_resilient_mode_ ? 0 : VpxReadLiteral(2);
|
||||
if (intra_only_) {
|
||||
if (!ValidateVp9SyncCode()) {
|
||||
fprintf(stderr, "Invalid Sync code!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile_ > 0) {
|
||||
ParseColorSpace();
|
||||
} 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).
|
||||
color_space_ = kVpxCsBt601;
|
||||
color_range_ = kVpxCrStudioRange;
|
||||
subsampling_y_ = subsampling_x_ = 1;
|
||||
bit_depth_ = 8;
|
||||
}
|
||||
|
||||
refresh_frame_flags_ = VpxReadLiteral(kRefFrames);
|
||||
ParseFrameResolution();
|
||||
} else {
|
||||
refresh_frame_flags_ = VpxReadLiteral(kRefFrames);
|
||||
for (int i = 0; i < kRefsPerFrame; ++i) {
|
||||
VpxReadLiteral(kRefFrames_LOG2); // Consume ref.
|
||||
ReadBit(); // Consume ref sign bias.
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < kRefsPerFrame; ++i) {
|
||||
if (ReadBit()) {
|
||||
// Found previous reference, width and height did not change since
|
||||
// last frame.
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
ParseFrameResolution();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int Vp9HeaderParser::ReadBit() {
|
||||
const size_t off = bit_offset_;
|
||||
const size_t byte_offset = off >> 3;
|
||||
const int bit_shift = 7 - static_cast<int>(off & 0x7);
|
||||
if (byte_offset < frame_size_) {
|
||||
const int bit = (frame_[byte_offset] >> bit_shift) & 1;
|
||||
bit_offset_++;
|
||||
return bit;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Vp9HeaderParser::VpxReadLiteral(int bits) {
|
||||
int value = 0;
|
||||
for (int bit = bits - 1; bit >= 0; --bit)
|
||||
value |= ReadBit() << bit;
|
||||
return value;
|
||||
}
|
||||
|
||||
bool Vp9HeaderParser::ValidateVp9SyncCode() {
|
||||
const int sync_code_0 = VpxReadLiteral(8);
|
||||
const int sync_code_1 = VpxReadLiteral(8);
|
||||
const int sync_code_2 = VpxReadLiteral(8);
|
||||
return (sync_code_0 == 0x49 && sync_code_1 == 0x83 && sync_code_2 == 0x42);
|
||||
}
|
||||
|
||||
void Vp9HeaderParser::ParseColorSpace() {
|
||||
bit_depth_ = 0;
|
||||
if (profile_ >= 2)
|
||||
bit_depth_ = ReadBit() ? 12 : 10;
|
||||
else
|
||||
bit_depth_ = 8;
|
||||
color_space_ = VpxReadLiteral(3);
|
||||
if (color_space_ != kVpxCsSrgb) {
|
||||
color_range_ = ReadBit();
|
||||
if (profile_ == 1 || profile_ == 3) {
|
||||
subsampling_x_ = ReadBit();
|
||||
subsampling_y_ = ReadBit();
|
||||
ReadBit();
|
||||
} else {
|
||||
subsampling_y_ = subsampling_x_ = 1;
|
||||
}
|
||||
} else {
|
||||
color_range_ = kVpxCrFullRange;
|
||||
if (profile_ == 1 || profile_ == 3) {
|
||||
subsampling_y_ = subsampling_x_ = 0;
|
||||
ReadBit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Vp9HeaderParser::ParseFrameResolution() {
|
||||
width_ = VpxReadLiteral(16) + 1;
|
||||
height_ = VpxReadLiteral(16) + 1;
|
||||
}
|
||||
|
||||
void Vp9HeaderParser::ParseFrameParallelMode() {
|
||||
if (ReadBit()) {
|
||||
VpxReadLiteral(16); // display width
|
||||
VpxReadLiteral(16); // display height
|
||||
}
|
||||
if (!error_resilient_mode_) {
|
||||
ReadBit(); // Consume refresh frame context
|
||||
frame_parallel_mode_ = ReadBit();
|
||||
} else {
|
||||
frame_parallel_mode_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Vp9HeaderParser::ParseTileInfo() {
|
||||
VpxReadLiteral(2); // Consume frame context index
|
||||
|
||||
// loopfilter
|
||||
VpxReadLiteral(6); // Consume filter level
|
||||
VpxReadLiteral(3); // Consume sharpness level
|
||||
|
||||
const bool mode_ref_delta_enabled = ReadBit();
|
||||
if (mode_ref_delta_enabled) {
|
||||
const bool mode_ref_delta_update = ReadBit();
|
||||
if (mode_ref_delta_update) {
|
||||
const int kMaxRefLFDeltas = 4;
|
||||
for (int i = 0; i < kMaxRefLFDeltas; ++i) {
|
||||
if (ReadBit())
|
||||
VpxReadLiteral(7); // Consume ref_deltas + sign
|
||||
}
|
||||
|
||||
const int kMaxModeDeltas = 2;
|
||||
for (int i = 0; i < kMaxModeDeltas; ++i) {
|
||||
if (ReadBit())
|
||||
VpxReadLiteral(7); // Consume mode_delta + sign
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// quantization
|
||||
VpxReadLiteral(8); // Consume base_q
|
||||
SkipDeltaQ(); // y dc
|
||||
SkipDeltaQ(); // uv ac
|
||||
SkipDeltaQ(); // uv dc
|
||||
|
||||
// segmentation
|
||||
const bool segmentation_enabled = ReadBit();
|
||||
if (!segmentation_enabled) {
|
||||
const int aligned_width = AlignPowerOfTwo(width_, kMiSizeLog2);
|
||||
const int mi_cols = aligned_width >> kMiSizeLog2;
|
||||
const int aligned_mi_cols = AlignPowerOfTwo(mi_cols, kMiSizeLog2);
|
||||
const int sb_cols = aligned_mi_cols >> 3; // to_sbs(mi_cols);
|
||||
int min_log2_n_tiles, max_log2_n_tiles;
|
||||
|
||||
for (max_log2_n_tiles = 0;
|
||||
(sb_cols >> max_log2_n_tiles) >= kMinTileWidthB64;
|
||||
max_log2_n_tiles++) {
|
||||
}
|
||||
max_log2_n_tiles--;
|
||||
if (max_log2_n_tiles < 0)
|
||||
max_log2_n_tiles = 0;
|
||||
|
||||
for (min_log2_n_tiles = 0; (kMaxTileWidthB64 << min_log2_n_tiles) < sb_cols;
|
||||
min_log2_n_tiles++) {
|
||||
}
|
||||
|
||||
// columns
|
||||
const int max_log2_tile_cols = max_log2_n_tiles;
|
||||
const int min_log2_tile_cols = min_log2_n_tiles;
|
||||
int max_ones = max_log2_tile_cols - min_log2_tile_cols;
|
||||
int log2_tile_cols = min_log2_tile_cols;
|
||||
while (max_ones-- && ReadBit())
|
||||
log2_tile_cols++;
|
||||
|
||||
// rows
|
||||
int log2_tile_rows = ReadBit();
|
||||
if (log2_tile_rows)
|
||||
log2_tile_rows += ReadBit();
|
||||
|
||||
row_tiles_ = 1 << log2_tile_rows;
|
||||
column_tiles_ = 1 << log2_tile_cols;
|
||||
}
|
||||
}
|
||||
|
||||
void Vp9HeaderParser::SkipDeltaQ() {
|
||||
if (ReadBit())
|
||||
VpxReadLiteral(4);
|
||||
}
|
||||
|
||||
int Vp9HeaderParser::AlignPowerOfTwo(int value, int n) {
|
||||
return (((value) + ((1 << (n)) - 1)) & ~((1 << (n)) - 1));
|
||||
}
|
||||
|
||||
} // namespace vp9_parser
|
122
common/vp9_header_parser.h
Normal file
122
common/vp9_header_parser.h
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
#ifndef LIBWEBM_COMMON_VP9_HEADER_PARSER_H_
|
||||
#define LIBWEBM_COMMON_VP9_HEADER_PARSER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace vp9_parser {
|
||||
|
||||
const int kVp9FrameMarker = 2;
|
||||
const int kMinTileWidthB64 = 4;
|
||||
const int kMaxTileWidthB64 = 64;
|
||||
const int kRefFrames = 8;
|
||||
const int kRefsPerFrame = 3;
|
||||
const int kRefFrames_LOG2 = 3;
|
||||
const int kVpxCsBt601 = 1;
|
||||
const int kVpxCsSrgb = 7;
|
||||
const int kVpxCrStudioRange = 0;
|
||||
const int kVpxCrFullRange = 1;
|
||||
const int kMiSizeLog2 = 3;
|
||||
|
||||
// Class to parse the header of a VP9 frame.
|
||||
class Vp9HeaderParser {
|
||||
public:
|
||||
Vp9HeaderParser()
|
||||
: frame_(NULL),
|
||||
frame_size_(0),
|
||||
bit_offset_(0),
|
||||
profile_(-1),
|
||||
show_existing_frame_(0),
|
||||
key_(0),
|
||||
altref_(0),
|
||||
error_resilient_mode_(0),
|
||||
intra_only_(0),
|
||||
reset_frame_context_(0),
|
||||
bit_depth_(0),
|
||||
color_space_(0),
|
||||
color_range_(0),
|
||||
subsampling_x_(0),
|
||||
subsampling_y_(0),
|
||||
refresh_frame_flags_(0),
|
||||
width_(0),
|
||||
height_(0),
|
||||
row_tiles_(0),
|
||||
column_tiles_(0),
|
||||
frame_parallel_mode_(0) {}
|
||||
|
||||
// Set the compressed VP9 frame. This must be called before
|
||||
// ParseUncompressedHeader.
|
||||
bool SetFrame(const uint8_t* frame, size_t length);
|
||||
|
||||
// Parse the VP9 uncompressed header.
|
||||
bool ParseUncompressedHeader();
|
||||
|
||||
size_t frame_size() const { return frame_size_; }
|
||||
int key() const { return key_; }
|
||||
int altref() const { return altref_; }
|
||||
int bit_depth() const { return bit_depth_; }
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
int refresh_frame_flags() const { return refresh_frame_flags_; }
|
||||
int row_tiles() const { return row_tiles_; }
|
||||
int column_tiles() const { return column_tiles_; }
|
||||
int frame_parallel_mode() const { return frame_parallel_mode_; }
|
||||
|
||||
private:
|
||||
// Returns the next bit of the frame.
|
||||
int ReadBit();
|
||||
|
||||
// Returns the next |bits| of the frame.
|
||||
int VpxReadLiteral(int bits);
|
||||
|
||||
// Returns true if the vp9 sync code is valid.
|
||||
bool ValidateVp9SyncCode();
|
||||
|
||||
// Parses bit_depth_, color_space_, subsampling_x_, subsampling_y_, and
|
||||
// color_range_.
|
||||
void ParseColorSpace();
|
||||
|
||||
// Parses width and height of the frame.
|
||||
void ParseFrameResolution();
|
||||
|
||||
// Parses frame_parallel_mode_. This function skips over some features.
|
||||
void ParseFrameParallelMode();
|
||||
|
||||
// Parses row and column tiles. This function skips over some features.
|
||||
void ParseTileInfo();
|
||||
void SkipDeltaQ();
|
||||
int AlignPowerOfTwo(int value, int n);
|
||||
|
||||
const uint8_t* frame_;
|
||||
size_t frame_size_;
|
||||
size_t bit_offset_;
|
||||
int profile_;
|
||||
int show_existing_frame_;
|
||||
int key_;
|
||||
int altref_;
|
||||
int error_resilient_mode_;
|
||||
int intra_only_;
|
||||
int reset_frame_context_;
|
||||
int bit_depth_;
|
||||
int color_space_;
|
||||
int color_range_;
|
||||
int subsampling_x_;
|
||||
int subsampling_y_;
|
||||
int refresh_frame_flags_;
|
||||
int width_;
|
||||
int height_;
|
||||
int row_tiles_;
|
||||
int column_tiles_;
|
||||
int frame_parallel_mode_;
|
||||
};
|
||||
|
||||
} // namespace vp9_parser
|
||||
|
||||
#endif // LIBWEBM_COMMON_VP9_HEADER_PARSER_H_
|
144
common/vp9_header_parser_tests.cc
Normal file
144
common/vp9_header_parser_tests.cc
Normal file
@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
#include "common/vp9_header_parser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "common/hdr_util.h"
|
||||
#include "mkvparser/mkvparser.h"
|
||||
#include "mkvparser/mkvreader.h"
|
||||
#include "testing/test_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class Vp9HeaderParserTests : public ::testing::Test {
|
||||
public:
|
||||
Vp9HeaderParserTests() : is_reader_open_(false), segment_(NULL) {}
|
||||
|
||||
~Vp9HeaderParserTests() override {
|
||||
CloseReader();
|
||||
if (segment_ != NULL) {
|
||||
delete segment_;
|
||||
segment_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CloseReader() {
|
||||
if (is_reader_open_) {
|
||||
reader_.Close();
|
||||
}
|
||||
is_reader_open_ = false;
|
||||
}
|
||||
|
||||
void CreateAndLoadSegment(const std::string& filename,
|
||||
int expected_doc_type_ver) {
|
||||
filename_ = test::GetTestFilePath(filename);
|
||||
ASSERT_EQ(0, reader_.Open(filename_.c_str()));
|
||||
is_reader_open_ = true;
|
||||
pos_ = 0;
|
||||
mkvparser::EBMLHeader ebml_header;
|
||||
ebml_header.Parse(&reader_, pos_);
|
||||
ASSERT_EQ(1, ebml_header.m_version);
|
||||
ASSERT_EQ(1, ebml_header.m_readVersion);
|
||||
ASSERT_STREQ("webm", ebml_header.m_docType);
|
||||
ASSERT_EQ(expected_doc_type_ver, ebml_header.m_docTypeVersion);
|
||||
ASSERT_EQ(2, ebml_header.m_docTypeReadVersion);
|
||||
ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, segment_));
|
||||
ASSERT_FALSE(HasFailure());
|
||||
ASSERT_GE(0, segment_->Load());
|
||||
}
|
||||
|
||||
void CreateAndLoadSegment(const std::string& filename) {
|
||||
CreateAndLoadSegment(filename, 2);
|
||||
}
|
||||
|
||||
void ProcessTheFrames() {
|
||||
unsigned char* data = NULL;
|
||||
size_t data_len = 0;
|
||||
const mkvparser::Tracks* const parser_tracks = segment_->GetTracks();
|
||||
ASSERT_TRUE(parser_tracks != NULL);
|
||||
const mkvparser::Cluster* cluster = segment_->GetFirst();
|
||||
ASSERT_TRUE(cluster != NULL);
|
||||
|
||||
while ((cluster != NULL) && !cluster->EOS()) {
|
||||
const mkvparser::BlockEntry* block_entry;
|
||||
long status = cluster->GetFirst(block_entry); // NOLINT
|
||||
ASSERT_EQ(0, status);
|
||||
|
||||
while ((block_entry != NULL) && !block_entry->EOS()) {
|
||||
const mkvparser::Block* const block = block_entry->GetBlock();
|
||||
ASSERT_TRUE(block != NULL);
|
||||
const long long trackNum = block->GetTrackNumber(); // NOLINT
|
||||
const mkvparser::Track* const parser_track =
|
||||
parser_tracks->GetTrackByNumber(
|
||||
static_cast<unsigned long>(trackNum)); // NOLINT
|
||||
ASSERT_TRUE(parser_track != NULL);
|
||||
const long long track_type = parser_track->GetType(); // NOLINT
|
||||
|
||||
if (track_type == mkvparser::Track::kVideo) {
|
||||
const int frame_count = block->GetFrameCount();
|
||||
|
||||
for (int i = 0; i < frame_count; ++i) {
|
||||
const mkvparser::Block::Frame& frame = block->GetFrame(i);
|
||||
|
||||
if (static_cast<size_t>(frame.len) > data_len) {
|
||||
delete[] data;
|
||||
data = new unsigned char[frame.len];
|
||||
ASSERT_TRUE(data != NULL);
|
||||
data_len = static_cast<size_t>(frame.len);
|
||||
}
|
||||
ASSERT_FALSE(frame.Read(&reader_, data));
|
||||
parser_.SetFrame(data, data_len);
|
||||
parser_.ParseUncompressedHeader();
|
||||
}
|
||||
}
|
||||
|
||||
status = cluster->GetNext(block_entry, block_entry);
|
||||
ASSERT_EQ(0, status);
|
||||
}
|
||||
|
||||
cluster = segment_->GetNext(cluster);
|
||||
}
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
protected:
|
||||
mkvparser::MkvReader reader_;
|
||||
bool is_reader_open_;
|
||||
mkvparser::Segment* segment_;
|
||||
std::string filename_;
|
||||
long long pos_; // NOLINT
|
||||
vp9_parser::Vp9HeaderParser parser_;
|
||||
};
|
||||
|
||||
TEST_F(Vp9HeaderParserTests, VideoOnlyFile) {
|
||||
CreateAndLoadSegment("test_stereo_left_right.webm");
|
||||
ProcessTheFrames();
|
||||
EXPECT_EQ(256, parser_.width());
|
||||
EXPECT_EQ(144, parser_.height());
|
||||
EXPECT_EQ(1, parser_.column_tiles());
|
||||
EXPECT_EQ(0, parser_.frame_parallel_mode());
|
||||
}
|
||||
|
||||
TEST_F(Vp9HeaderParserTests, Muxed) {
|
||||
CreateAndLoadSegment("bbb_480p_vp9_opus_1second.webm", 4);
|
||||
ProcessTheFrames();
|
||||
EXPECT_EQ(854, parser_.width());
|
||||
EXPECT_EQ(480, parser_.height());
|
||||
EXPECT_EQ(2, parser_.column_tiles());
|
||||
EXPECT_EQ(1, parser_.frame_parallel_mode());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Loading…
Reference in New Issue
Block a user