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:
Frank Galligan 2016-04-15 12:40:41 -07:00
parent a004fefc56
commit 8bb68c2b3e
4 changed files with 536 additions and 0 deletions

View File

@ -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
View 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
View 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_

View 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();
}