diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-07-31 15:50:41 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-08-30 12:35:23 +0000 |
commit | 7b2ffa587235a47d4094787d72f38102089f402a (patch) | |
tree | 30e82af9cbab08a7fa028bb18f4f2987a3f74dfa /chromium/media/parsers | |
parent | d94af01c90575348c4e81a418257f254b6f8d225 (diff) | |
download | qtwebengine-chromium-7b2ffa587235a47d4094787d72f38102089f402a.tar.gz |
BASELINE: Update Chromium to 76.0.3809.94
Change-Id: I321c3f5f929c105aec0f98c5091ef6108822e647
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/media/parsers')
-rw-r--r-- | chromium/media/parsers/BUILD.gn | 82 | ||||
-rw-r--r-- | chromium/media/parsers/jpeg_parser.cc | 574 | ||||
-rw-r--r-- | chromium/media/parsers/jpeg_parser.h | 164 | ||||
-rw-r--r-- | chromium/media/parsers/jpeg_parser_picture_fuzzertest.cc | 22 | ||||
-rw-r--r-- | chromium/media/parsers/jpeg_parser_unittest.cc | 138 | ||||
-rw-r--r-- | chromium/media/parsers/media_parsers_export.h | 12 | ||||
-rw-r--r-- | chromium/media/parsers/vp8_bool_decoder.cc | 209 | ||||
-rw-r--r-- | chromium/media/parsers/vp8_bool_decoder.h | 135 | ||||
-rw-r--r-- | chromium/media/parsers/vp8_bool_decoder_unittest.cc | 126 | ||||
-rw-r--r-- | chromium/media/parsers/vp8_parser.cc | 876 | ||||
-rw-r--r-- | chromium/media/parsers/vp8_parser.h | 208 | ||||
-rw-r--r-- | chromium/media/parsers/vp8_parser_fuzzertest.cc | 31 | ||||
-rw-r--r-- | chromium/media/parsers/vp8_parser_unittest.cc | 50 | ||||
-rw-r--r-- | chromium/media/parsers/webp_parser.cc | 131 | ||||
-rw-r--r-- | chromium/media/parsers/webp_parser.h | 38 | ||||
-rw-r--r-- | chromium/media/parsers/webp_parser_fuzzertest.cc | 24 | ||||
-rw-r--r-- | chromium/media/parsers/webp_parser_unittest.cc | 327 |
17 files changed, 3147 insertions, 0 deletions
diff --git a/chromium/media/parsers/BUILD.gn b/chromium/media/parsers/BUILD.gn new file mode 100644 index 00000000000..140d1aae981 --- /dev/null +++ b/chromium/media/parsers/BUILD.gn @@ -0,0 +1,82 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//media/media_options.gni") + +component("parsers") { + sources = [ + "jpeg_parser.cc", + "jpeg_parser.h", + "media_parsers_export.h", + "vp8_bool_decoder.cc", + "vp8_bool_decoder.h", + "vp8_parser.cc", + "vp8_parser.h", + "webp_parser.cc", + "webp_parser.h", + ] + defines = [ "IS_MEDIA_PARSER_IMPL" ] + deps = [ + "//base", + ] + + # This target is used in GPU IPC code and cannot depend on any //media code. + assert_no_deps = [ + "//media", + "//media:shared_memory_support", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "jpeg_parser_unittest.cc", + "vp8_bool_decoder_unittest.cc", + "vp8_parser_unittest.cc", + "webp_parser_unittest.cc", + ] + deps = [ + ":parsers", + "//base", + "//media:test_support", + "//testing/gtest", + ] +} + +fuzzer_test("media_jpeg_parser_picture_fuzzer") { + sources = [ + "jpeg_parser_picture_fuzzertest.cc", + ] + deps = [ + ":parsers", + "//base", + ] + seed_corpus = "//media/test/data" + dict = "//media/test/jpeg.dict" +} + +fuzzer_test("media_vp8_parser_fuzzer") { + sources = [ + "vp8_parser_fuzzertest.cc", + ] + deps = [ + ":parsers", + "//base", + "//media:test_support", + ] + libfuzzer_options = [ "max_len = 400000" ] + dict = "//media/test/vp8.dict" +} + +fuzzer_test("media_webp_parser_fuzzer") { + sources = [ + "webp_parser_fuzzertest.cc", + ] + deps = [ + ":parsers", + "//base", + ] + seed_corpus = "//media/test/data" + dict = "//media/test/webp.dict" +} diff --git a/chromium/media/parsers/jpeg_parser.cc b/chromium/media/parsers/jpeg_parser.cc new file mode 100644 index 00000000000..344f7c36995 --- /dev/null +++ b/chromium/media/parsers/jpeg_parser.cc @@ -0,0 +1,574 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/parsers/jpeg_parser.h" + +#include "base/big_endian.h" +#include "base/logging.h" +#include "base/stl_util.h" + +using base::BigEndianReader; + +#define READ_U8_OR_RETURN_FALSE(out) \ + do { \ + uint8_t _out; \ + if (!reader.ReadU8(&_out)) { \ + DVLOG(1) \ + << "Error in stream: unexpected EOS while trying to read " #out; \ + return false; \ + } \ + *(out) = _out; \ + } while (0) + +#define READ_U16_OR_RETURN_FALSE(out) \ + do { \ + uint16_t _out; \ + if (!reader.ReadU16(&_out)) { \ + DVLOG(1) \ + << "Error in stream: unexpected EOS while trying to read " #out; \ + return false; \ + } \ + *(out) = _out; \ + } while (0) + +namespace media { + +const JpegHuffmanTable kDefaultDcTable[kJpegMaxHuffmanTableNumBaseline] = { + // luminance DC coefficients + { + true, + {0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b}, + }, + // chrominance DC coefficients + { + true, + {0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb}, + }, +}; + +const JpegHuffmanTable kDefaultAcTable[kJpegMaxHuffmanTableNumBaseline] = { + // luminance AC coefficients + { + true, + {0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d}, + {0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, + 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa}, + }, + // chrominance AC coefficients + { + true, + {0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77}, + {0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, + 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa}, + }, +}; + +constexpr uint8_t kZigZag8x8[64] = { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63}; + +constexpr JpegQuantizationTable kDefaultQuantTable[2] = { + // Table K.1 Luminance quantization table values. + { + true, + {16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99}, + }, + // Table K.2 Chrominance quantization table values. + { + true, + {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}, + }, +}; + +static bool InRange(int value, int a, int b) { + return a <= value && value <= b; +} + +// Round up |value| to multiple of |mul|. |value| must be non-negative. +// |mul| must be positive. +static int RoundUp(int value, int mul) { + DCHECK_GE(value, 0); + DCHECK_GE(mul, 1); + return (value + mul - 1) / mul * mul; +} + +// |frame_header| is already initialized to 0 in ParseJpegPicture. +static bool ParseSOF(const char* buffer, + size_t length, + JpegFrameHeader* frame_header) { + // Spec B.2.2 Frame header syntax + DCHECK(buffer); + DCHECK(frame_header); + BigEndianReader reader(buffer, length); + + uint8_t precision; + READ_U8_OR_RETURN_FALSE(&precision); + READ_U16_OR_RETURN_FALSE(&frame_header->visible_height); + READ_U16_OR_RETURN_FALSE(&frame_header->visible_width); + READ_U8_OR_RETURN_FALSE(&frame_header->num_components); + + if (precision != 8) { + DLOG(ERROR) << "Only support 8-bit precision, not " + << static_cast<int>(precision) << " bit for baseline"; + return false; + } + if (!InRange(frame_header->num_components, 1, + base::size(frame_header->components))) { + DLOG(ERROR) << "num_components=" + << static_cast<int>(frame_header->num_components) + << " is not supported"; + return false; + } + + int max_h_factor = 0; + int max_v_factor = 0; + for (size_t i = 0; i < frame_header->num_components; i++) { + JpegComponent& component = frame_header->components[i]; + READ_U8_OR_RETURN_FALSE(&component.id); + if (component.id > frame_header->num_components) { + DLOG(ERROR) << "component id (" << static_cast<int>(component.id) + << ") should be <= num_components (" + << static_cast<int>(frame_header->num_components) << ")"; + return false; + } + uint8_t hv; + READ_U8_OR_RETURN_FALSE(&hv); + component.horizontal_sampling_factor = hv / 16; + component.vertical_sampling_factor = hv % 16; + if (component.horizontal_sampling_factor > max_h_factor) + max_h_factor = component.horizontal_sampling_factor; + if (component.vertical_sampling_factor > max_v_factor) + max_v_factor = component.vertical_sampling_factor; + if (!InRange(component.horizontal_sampling_factor, 1, 4)) { + DVLOG(1) << "Invalid horizontal sampling factor " + << static_cast<int>(component.horizontal_sampling_factor); + return false; + } + if (!InRange(component.vertical_sampling_factor, 1, 4)) { + DVLOG(1) << "Invalid vertical sampling factor " + << static_cast<int>(component.horizontal_sampling_factor); + return false; + } + READ_U8_OR_RETURN_FALSE(&component.quantization_table_selector); + } + + // The size of data unit is 8*8 and the coded size should be extended + // to complete minimum coded unit, MCU. See Spec A.2. + frame_header->coded_width = + RoundUp(frame_header->visible_width, max_h_factor * 8); + frame_header->coded_height = + RoundUp(frame_header->visible_height, max_v_factor * 8); + + return true; +} + +// |q_table| is already initialized to 0 in ParseJpegPicture. +static bool ParseDQT(const char* buffer, + size_t length, + JpegQuantizationTable* q_table) { + // Spec B.2.4.1 Quantization table-specification syntax + DCHECK(buffer); + DCHECK(q_table); + BigEndianReader reader(buffer, length); + while (reader.remaining() > 0) { + uint8_t precision_and_table_id; + READ_U8_OR_RETURN_FALSE(&precision_and_table_id); + uint8_t precision = precision_and_table_id / 16; + uint8_t table_id = precision_and_table_id % 16; + if (!InRange(precision, 0, 1)) { + DVLOG(1) << "Invalid precision " << static_cast<int>(precision); + return false; + } + if (precision == 1) { // 1 means 16-bit precision + DLOG(ERROR) << "An 8-bit DCT-based process shall not use a 16-bit " + << "precision quantization table"; + return false; + } + if (table_id >= kJpegMaxQuantizationTableNum) { + DLOG(ERROR) << "Quantization table id (" << static_cast<int>(table_id) + << ") exceeded " << kJpegMaxQuantizationTableNum; + return false; + } + + if (!reader.ReadBytes(&q_table[table_id].value, + sizeof(q_table[table_id].value))) + return false; + q_table[table_id].valid = true; + } + return true; +} + +// |dc_table| and |ac_table| are already initialized to 0 in ParseJpegPicture. +static bool ParseDHT(const char* buffer, + size_t length, + JpegHuffmanTable* dc_table, + JpegHuffmanTable* ac_table) { + // Spec B.2.4.2 Huffman table-specification syntax + DCHECK(buffer); + DCHECK(dc_table); + DCHECK(ac_table); + BigEndianReader reader(buffer, length); + while (reader.remaining() > 0) { + uint8_t table_class_and_id; + READ_U8_OR_RETURN_FALSE(&table_class_and_id); + int table_class = table_class_and_id / 16; + int table_id = table_class_and_id % 16; + if (!InRange(table_class, 0, 1)) { + DVLOG(1) << "Invalid table class " << table_class; + return false; + } + if (table_id >= 2) { + DLOG(ERROR) << "Table id(" << table_id + << ") >= 2 is invalid for baseline profile"; + return false; + } + + JpegHuffmanTable* table; + if (table_class == 1) + table = &ac_table[table_id]; + else + table = &dc_table[table_id]; + + size_t count = 0; + if (!reader.ReadBytes(&table->code_length, sizeof(table->code_length))) + return false; + for (size_t i = 0; i < base::size(table->code_length); i++) + count += table->code_length[i]; + + if (!InRange(count, 0, sizeof(table->code_value))) { + DVLOG(1) << "Invalid code count " << count; + return false; + } + if (!reader.ReadBytes(&table->code_value, count)) + return false; + table->valid = true; + } + return true; +} + +static bool ParseDRI(const char* buffer, + size_t length, + uint16_t* restart_interval) { + // Spec B.2.4.4 Restart interval definition syntax + DCHECK(buffer); + DCHECK(restart_interval); + BigEndianReader reader(buffer, length); + return reader.ReadU16(restart_interval) && reader.remaining() == 0; +} + +// |scan| is already initialized to 0 in ParseJpegPicture. +static bool ParseSOS(const char* buffer, + size_t length, + const JpegFrameHeader& frame_header, + JpegScanHeader* scan) { + // Spec B.2.3 Scan header syntax + DCHECK(buffer); + DCHECK(scan); + BigEndianReader reader(buffer, length); + READ_U8_OR_RETURN_FALSE(&scan->num_components); + if (scan->num_components != frame_header.num_components) { + DLOG(ERROR) << "The number of scan components (" + << static_cast<int>(scan->num_components) + << ") mismatches the number of image components (" + << static_cast<int>(frame_header.num_components) << ")"; + return false; + } + + for (int i = 0; i < scan->num_components; i++) { + JpegScanHeader::Component* component = &scan->components[i]; + READ_U8_OR_RETURN_FALSE(&component->component_selector); + uint8_t dc_and_ac_selector; + READ_U8_OR_RETURN_FALSE(&dc_and_ac_selector); + component->dc_selector = dc_and_ac_selector / 16; + component->ac_selector = dc_and_ac_selector % 16; + if (component->component_selector != frame_header.components[i].id) { + DLOG(ERROR) << "component selector mismatches image component id"; + return false; + } + if (component->dc_selector >= kJpegMaxHuffmanTableNumBaseline) { + DLOG(ERROR) << "DC selector (" << static_cast<int>(component->dc_selector) + << ") should be 0 or 1 for baseline mode"; + return false; + } + if (component->ac_selector >= kJpegMaxHuffmanTableNumBaseline) { + DLOG(ERROR) << "AC selector (" << static_cast<int>(component->ac_selector) + << ") should be 0 or 1 for baseline mode"; + return false; + } + } + + // Unused fields, only for value checking. + uint8_t spectral_selection_start; + uint8_t spectral_selection_end; + uint8_t point_transform; + READ_U8_OR_RETURN_FALSE(&spectral_selection_start); + READ_U8_OR_RETURN_FALSE(&spectral_selection_end); + READ_U8_OR_RETURN_FALSE(&point_transform); + if (spectral_selection_start != 0 || spectral_selection_end != 63) { + DLOG(ERROR) << "Spectral selection should be 0,63 for baseline mode"; + return false; + } + if (point_transform != 0) { + DLOG(ERROR) << "Point transform should be 0 for baseline mode"; + return false; + } + + return true; +} + +// |eoi_begin_ptr| will point to the beginning of the EOI marker (the FF byte) +// and |eoi_end_ptr| will point to the end of image (right after the end of the +// EOI marker) after search succeeds. Returns true on EOI marker found, or false +// otherwise. +static bool SearchEOI(const char* buffer, + size_t length, + const char** eoi_begin_ptr, + const char** eoi_end_ptr) { + DCHECK(buffer); + DCHECK(eoi_begin_ptr); + DCHECK(eoi_end_ptr); + BigEndianReader reader(buffer, length); + uint8_t marker2; + + while (reader.remaining() > 0) { + const char* marker1_ptr = static_cast<const char*>( + memchr(reader.ptr(), JPEG_MARKER_PREFIX, reader.remaining())); + if (!marker1_ptr) + return false; + reader.Skip(marker1_ptr - reader.ptr() + 1); + + do { + READ_U8_OR_RETURN_FALSE(&marker2); + } while (marker2 == JPEG_MARKER_PREFIX); // skip fill bytes + + switch (marker2) { + // Compressed data escape. + case 0x00: + break; + // Restart + case JPEG_RST0: + case JPEG_RST1: + case JPEG_RST2: + case JPEG_RST3: + case JPEG_RST4: + case JPEG_RST5: + case JPEG_RST6: + case JPEG_RST7: + break; + case JPEG_EOI: + *eoi_begin_ptr = marker1_ptr; + *eoi_end_ptr = reader.ptr(); + return true; + default: + // Skip for other markers. + uint16_t size; + READ_U16_OR_RETURN_FALSE(&size); + if (size < sizeof(size)) { + DLOG(ERROR) << "Ill-formed JPEG. Segment size (" << size + << ") is smaller than size field (" << sizeof(size) + << ")"; + return false; + } + size -= sizeof(size); + + if (!reader.Skip(size)) { + DLOG(ERROR) << "Ill-formed JPEG. Remaining size (" + << reader.remaining() + << ") is smaller than header specified (" << size << ")"; + return false; + } + break; + } + } + return false; +} + +// |result| is already initialized to 0 in ParseJpegPicture. +static bool ParseSOI(const char* buffer, + size_t length, + JpegParseResult* result) { + // Spec B.2.1 High-level syntax + DCHECK(buffer); + DCHECK(result); + BigEndianReader reader(buffer, length); + uint8_t marker1; + uint8_t marker2; + bool has_marker_dqt = false; + bool has_marker_sos = false; + + // Once reached SOS, all neccesary data are parsed. + while (!has_marker_sos) { + READ_U8_OR_RETURN_FALSE(&marker1); + if (marker1 != JPEG_MARKER_PREFIX) + return false; + + do { + READ_U8_OR_RETURN_FALSE(&marker2); + } while (marker2 == JPEG_MARKER_PREFIX); // skip fill bytes + + uint16_t size; + READ_U16_OR_RETURN_FALSE(&size); + // The size includes the size field itself. + if (size < sizeof(size)) { + DLOG(ERROR) << "Ill-formed JPEG. Segment size (" << size + << ") is smaller than size field (" << sizeof(size) << ")"; + return false; + } + size -= sizeof(size); + + if (reader.remaining() < size) { + DLOG(ERROR) << "Ill-formed JPEG. Remaining size (" << reader.remaining() + << ") is smaller than header specified (" << size << ")"; + return false; + } + + switch (marker2) { + case JPEG_SOF0: + if (!ParseSOF(reader.ptr(), size, &result->frame_header)) { + DLOG(ERROR) << "ParseSOF failed"; + return false; + } + break; + case JPEG_SOF1: + case JPEG_SOF2: + case JPEG_SOF3: + case JPEG_SOF5: + case JPEG_SOF6: + case JPEG_SOF7: + case JPEG_SOF9: + case JPEG_SOF10: + case JPEG_SOF11: + case JPEG_SOF13: + case JPEG_SOF14: + case JPEG_SOF15: + DLOG(ERROR) << "Only SOF0 (baseline) is supported, but got SOF" + << (marker2 - JPEG_SOF0); + return false; + case JPEG_DQT: + if (!ParseDQT(reader.ptr(), size, result->q_table)) { + DLOG(ERROR) << "ParseDQT failed"; + return false; + } + has_marker_dqt = true; + break; + case JPEG_DHT: + if (!ParseDHT(reader.ptr(), size, result->dc_table, result->ac_table)) { + DLOG(ERROR) << "ParseDHT failed"; + return false; + } + break; + case JPEG_DRI: + if (!ParseDRI(reader.ptr(), size, &result->restart_interval)) { + DLOG(ERROR) << "ParseDRI failed"; + return false; + } + break; + case JPEG_SOS: + if (!ParseSOS(reader.ptr(), size, result->frame_header, + &result->scan)) { + DLOG(ERROR) << "ParseSOS failed"; + return false; + } + has_marker_sos = true; + break; + default: + DVLOG(4) << "unknown marker " << static_cast<int>(marker2); + break; + } + reader.Skip(size); + } + + if (!has_marker_dqt) { + DLOG(ERROR) << "No DQT marker found"; + return false; + } + + // Scan data follows scan header immediately. + result->data = reader.ptr(); + result->data_size = reader.remaining(); + return true; +} + +bool ParseJpegPicture(const uint8_t* buffer, + size_t length, + JpegParseResult* result) { + DCHECK(buffer); + DCHECK(result); + BigEndianReader reader(reinterpret_cast<const char*>(buffer), length); + memset(result, 0, sizeof(JpegParseResult)); + + uint8_t marker1, marker2; + READ_U8_OR_RETURN_FALSE(&marker1); + READ_U8_OR_RETURN_FALSE(&marker2); + if (marker1 != JPEG_MARKER_PREFIX || marker2 != JPEG_SOI) { + DLOG(ERROR) << "Not a JPEG"; + return false; + } + + if (!ParseSOI(reader.ptr(), reader.remaining(), result)) + return false; + + // Update the sizes: |result->data_size| should not include the EOI marker or + // beyond. + BigEndianReader eoi_reader(result->data, result->data_size); + const char* eoi_begin_ptr = nullptr; + const char* eoi_end_ptr = nullptr; + if (!SearchEOI(eoi_reader.ptr(), eoi_reader.remaining(), &eoi_begin_ptr, + &eoi_end_ptr)) { + DLOG(ERROR) << "SearchEOI failed"; + return false; + } + DCHECK(eoi_begin_ptr); + DCHECK(eoi_end_ptr); + result->data_size = eoi_begin_ptr - result->data; + result->image_size = eoi_end_ptr - reinterpret_cast<const char*>(buffer); + return true; +} + +// TODO(andrescj): this function no longer seems necessary. Fix call sites to +// use ParseJpegPicture() directly. +bool ParseJpegStream(const uint8_t* buffer, + size_t length, + JpegParseResult* result) { + DCHECK(buffer); + DCHECK(result); + return ParseJpegPicture(buffer, length, result); +} + +} // namespace media diff --git a/chromium/media/parsers/jpeg_parser.h b/chromium/media/parsers/jpeg_parser.h new file mode 100644 index 00000000000..bd054f555b8 --- /dev/null +++ b/chromium/media/parsers/jpeg_parser.h @@ -0,0 +1,164 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_PARSERS_JPEG_PARSER_H_ +#define MEDIA_PARSERS_JPEG_PARSER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "media/parsers/media_parsers_export.h" + +namespace media { + +// It's not a full featured JPEG parser implementation. It only parses JPEG +// baseline sequential process (invalid or progressive JPEGs should fail but not +// crash). For explanations of each struct and its members, see JPEG +// specification at http://www.w3.org/Graphics/JPEG/itu-t81.pdf. + +enum JpegMarker { + JPEG_SOF0 = 0xC0, // start of frame (baseline) + JPEG_SOF1 = 0xC1, // start of frame (extended sequential) + JPEG_SOF2 = 0xC2, // start of frame (progressive) + JPEG_SOF3 = 0xC3, // start of frame (lossless)) + JPEG_DHT = 0xC4, // define huffman table + JPEG_SOF5 = 0xC5, // start of frame (differential, sequential) + JPEG_SOF6 = 0xC6, // start of frame (differential, progressive) + JPEG_SOF7 = 0xC7, // start of frame (differential, lossless) + JPEG_SOF9 = 0xC9, // start of frame (arithmetic coding, extended) + JPEG_SOF10 = 0xCA, // start of frame (arithmetic coding, progressive) + JPEG_SOF11 = 0xCB, // start of frame (arithmetic coding, lossless) + JPEG_SOF13 = 0xCD, // start of frame (differential, arithmetic, sequential) + JPEG_SOF14 = 0xCE, // start of frame (differential, arithmetic, progressive) + JPEG_SOF15 = 0xCF, // start of frame (differential, arithmetic, lossless) + JPEG_RST0 = 0xD0, // restart + JPEG_RST1 = 0xD1, // restart + JPEG_RST2 = 0xD2, // restart + JPEG_RST3 = 0xD3, // restart + JPEG_RST4 = 0xD4, // restart + JPEG_RST5 = 0xD5, // restart + JPEG_RST6 = 0xD6, // restart + JPEG_RST7 = 0xD7, // restart + JPEG_SOI = 0xD8, // start of image + JPEG_EOI = 0xD9, // end of image + JPEG_SOS = 0xDA, // start of scan + JPEG_DQT = 0xDB, // define quantization table + JPEG_DRI = 0xDD, // define restart internal + JPEG_APP0 = 0xE0, // start of application segment (APP0) + JPEG_APP1 = 0xE1, // start of application segment (APP1) + JPEG_MARKER_PREFIX = 0xFF, // jpeg marker prefix +}; + +// JPEG format uses 2 bytes to denote the size of a segment, and the size +// includes the 2 bytes used for specifying it. Therefore, maximum data size +// allowed is: 65535 - 2 = 65533. +constexpr size_t kMaxMarkerSizeAllowed = 65533; + +// JPEG header only uses 2 bytes to represent width and height. +constexpr int kMaxDimension = 65535; + +constexpr size_t kDctSize = 64; +constexpr size_t kNumDcRunSizeBits = 16; +constexpr size_t kNumAcRunSizeBits = 16; +constexpr size_t kNumDcCodeWordsHuffVal = 12; +constexpr size_t kNumAcCodeWordsHuffVal = 162; +constexpr size_t kJpegDefaultHeaderSize = + 67 + (kDctSize * 2) + (kNumDcRunSizeBits * 2) + + (kNumDcCodeWordsHuffVal * 2) + (kNumAcRunSizeBits * 2) + + (kNumAcCodeWordsHuffVal * 2); +constexpr size_t kJFIFApp0Size = 16; + +const size_t kJpegMaxHuffmanTableNumBaseline = 2; +const size_t kJpegMaxComponents = 4; +const size_t kJpegMaxQuantizationTableNum = 4; + +// Parsing result of JPEG DHT marker. +struct JpegHuffmanTable { + bool valid; + uint8_t code_length[16]; + uint8_t code_value[162]; +}; + +// K.3.3.1 "Specification of typical tables for DC difference coding" +MEDIA_PARSERS_EXPORT +extern const JpegHuffmanTable kDefaultDcTable[kJpegMaxHuffmanTableNumBaseline]; + +// K.3.3.2 "Specification of typical tables for AC coefficient coding" +MEDIA_PARSERS_EXPORT +extern const JpegHuffmanTable kDefaultAcTable[kJpegMaxHuffmanTableNumBaseline]; + +// Parsing result of JPEG DQT marker. +struct JpegQuantizationTable { + bool valid; + uint8_t value[kDctSize]; // baseline only supports 8 bits quantization table +}; + +MEDIA_PARSERS_EXPORT extern const uint8_t kZigZag8x8[64]; + +// Table K.1 Luminance quantization table +// Table K.2 Chrominance quantization table +MEDIA_PARSERS_EXPORT +extern const JpegQuantizationTable kDefaultQuantTable[2]; + +// Parsing result of a JPEG component. +struct JpegComponent { + uint8_t id; + uint8_t horizontal_sampling_factor; + uint8_t vertical_sampling_factor; + uint8_t quantization_table_selector; +}; + +// Parsing result of a JPEG SOF marker. +struct JpegFrameHeader { + uint16_t visible_width; + uint16_t visible_height; + uint16_t coded_width; + uint16_t coded_height; + uint8_t num_components; + JpegComponent components[kJpegMaxComponents]; +}; + +// Parsing result of JPEG SOS marker. +struct JpegScanHeader { + uint8_t num_components; + struct Component { + uint8_t component_selector; + uint8_t dc_selector; + uint8_t ac_selector; + } components[kJpegMaxComponents]; +}; + +struct JpegParseResult { + JpegFrameHeader frame_header; + JpegHuffmanTable dc_table[kJpegMaxHuffmanTableNumBaseline]; + JpegHuffmanTable ac_table[kJpegMaxHuffmanTableNumBaseline]; + JpegQuantizationTable q_table[kJpegMaxQuantizationTableNum]; + uint16_t restart_interval; + JpegScanHeader scan; + const char* data; + // The size of compressed data of the first image. + size_t data_size; + // The size of the first entire image including header. + size_t image_size; +}; + +// Parses JPEG picture in |buffer| with |length|. Returns true iff header is +// valid and JPEG baseline sequential process is present. If parsed +// successfully, |result| is the parsed result. +MEDIA_PARSERS_EXPORT +bool ParseJpegPicture(const uint8_t* buffer, + size_t length, + JpegParseResult* result); + +// Parses the first image of JPEG stream in |buffer| with |length|. Returns +// true iff header is valid and JPEG baseline sequential process is present. +// If parsed successfully, |result| is the parsed result. +MEDIA_PARSERS_EXPORT +bool ParseJpegStream(const uint8_t* buffer, + size_t length, + JpegParseResult* result); + +} // namespace media + +#endif // MEDIA_PARSERS_JPEG_PARSER_H_ diff --git a/chromium/media/parsers/jpeg_parser_picture_fuzzertest.cc b/chromium/media/parsers/jpeg_parser_picture_fuzzertest.cc new file mode 100644 index 00000000000..31d9cae7e0f --- /dev/null +++ b/chromium/media/parsers/jpeg_parser_picture_fuzzertest.cc @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "base/logging.h" +#include "media/parsers/jpeg_parser.h" + +struct Environment { + Environment() { logging::SetMinLogLevel(logging::LOG_FATAL); } +}; + +Environment* env = new Environment(); + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + media::JpegParseResult result; + ParseJpegPicture(data, size, &result); + return 0; +} diff --git a/chromium/media/parsers/jpeg_parser_unittest.cc b/chromium/media/parsers/jpeg_parser_unittest.cc new file mode 100644 index 00000000000..72b9ae957aa --- /dev/null +++ b/chromium/media/parsers/jpeg_parser_unittest.cc @@ -0,0 +1,138 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include "base/at_exit.h" +#include "base/files/memory_mapped_file.h" +#include "base/path_service.h" +#include "media/base/test_data_util.h" +#include "media/parsers/jpeg_parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +TEST(JpegParserTest, Parsing) { + base::FilePath data_dir; + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + + // This sample frame is captured from Chromebook Pixel + base::FilePath file_path = data_dir.AppendASCII("media") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("pixel-1280x720.jpg"); + + base::MemoryMappedFile stream; + ASSERT_TRUE(stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + + JpegParseResult result; + ASSERT_TRUE(ParseJpegPicture(stream.data(), stream.length(), &result)); + + // Verify selected fields + + // SOF fields + EXPECT_EQ(1280, result.frame_header.visible_width); + EXPECT_EQ(720, result.frame_header.visible_height); + EXPECT_EQ(1280, result.frame_header.coded_width); + EXPECT_EQ(720, result.frame_header.coded_height); + EXPECT_EQ(3, result.frame_header.num_components); + EXPECT_EQ(1, result.frame_header.components[0].id); + EXPECT_EQ(2, result.frame_header.components[0].horizontal_sampling_factor); + EXPECT_EQ(1, result.frame_header.components[0].vertical_sampling_factor); + EXPECT_EQ(0, result.frame_header.components[0].quantization_table_selector); + EXPECT_EQ(2, result.frame_header.components[1].id); + EXPECT_EQ(1, result.frame_header.components[1].horizontal_sampling_factor); + EXPECT_EQ(1, result.frame_header.components[1].vertical_sampling_factor); + EXPECT_EQ(1, result.frame_header.components[1].quantization_table_selector); + EXPECT_EQ(3, result.frame_header.components[2].id); + EXPECT_EQ(1, result.frame_header.components[2].horizontal_sampling_factor); + EXPECT_EQ(1, result.frame_header.components[2].vertical_sampling_factor); + EXPECT_EQ(1, result.frame_header.components[2].quantization_table_selector); + + // DRI fields + EXPECT_EQ(0, result.restart_interval); + + // DQT fields + EXPECT_TRUE(result.q_table[0].valid); + EXPECT_TRUE(result.q_table[1].valid); + EXPECT_FALSE(result.q_table[2].valid); + EXPECT_FALSE(result.q_table[3].valid); + + // DHT fields (no DHT marker) + EXPECT_FALSE(result.dc_table[0].valid); + EXPECT_FALSE(result.ac_table[0].valid); + EXPECT_FALSE(result.dc_table[1].valid); + EXPECT_FALSE(result.ac_table[1].valid); + + // SOS fields + EXPECT_EQ(3, result.scan.num_components); + EXPECT_EQ(1, result.scan.components[0].component_selector); + EXPECT_EQ(0, result.scan.components[0].dc_selector); + EXPECT_EQ(0, result.scan.components[0].ac_selector); + EXPECT_EQ(2, result.scan.components[1].component_selector); + EXPECT_EQ(1, result.scan.components[1].dc_selector); + EXPECT_EQ(1, result.scan.components[1].ac_selector); + EXPECT_EQ(3, result.scan.components[2].component_selector); + EXPECT_EQ(1, result.scan.components[2].dc_selector); + EXPECT_EQ(1, result.scan.components[2].ac_selector); + EXPECT_EQ(121148u, result.data_size); + EXPECT_EQ(121358u, result.image_size); +} + +TEST(JpegParserTest, TrailingZerosShouldBeIgnored) { + base::FilePath data_dir; + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + base::FilePath file_path = + data_dir.AppendASCII("media") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("pixel-1280x720-trailing-zeros.jpg"); + + base::MemoryMappedFile stream; + ASSERT_TRUE(stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + + JpegParseResult result; + ASSERT_TRUE(ParseJpegPicture(stream.data(), stream.length(), &result)); + + // Verify selected fields + + // SOS fields + EXPECT_EQ(121148u, result.data_size); + EXPECT_EQ(121358u, result.image_size); +} + +TEST(JpegParserTest, CodedSizeNotEqualVisibleSize) { + base::FilePath data_dir; + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + + base::FilePath file_path = data_dir.AppendASCII("media") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("blank-1x1.jpg"); + + base::MemoryMappedFile stream; + ASSERT_TRUE(stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + + JpegParseResult result; + ASSERT_TRUE(ParseJpegPicture(stream.data(), stream.length(), &result)); + + EXPECT_EQ(1, result.frame_header.visible_width); + EXPECT_EQ(1, result.frame_header.visible_height); + // The sampling factor of the given image is 2:2, so coded size is 16x16 + EXPECT_EQ(16, result.frame_header.coded_width); + EXPECT_EQ(16, result.frame_header.coded_height); + EXPECT_EQ(2, result.frame_header.components[0].horizontal_sampling_factor); + EXPECT_EQ(2, result.frame_header.components[0].vertical_sampling_factor); +} + +TEST(JpegParserTest, ParsingFail) { + const uint8_t data[] = {0, 1, 2, 3}; // not jpeg + JpegParseResult result; + ASSERT_FALSE(ParseJpegPicture(data, sizeof(data), &result)); +} + +} // namespace media diff --git a/chromium/media/parsers/media_parsers_export.h b/chromium/media/parsers/media_parsers_export.h new file mode 100644 index 00000000000..72277ea7a92 --- /dev/null +++ b/chromium/media/parsers/media_parsers_export.h @@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_PARSERS_MEDIA_PARSERS_EXPORT_H_ +#define MEDIA_PARSERS_MEDIA_PARSERS_EXPORT_H_ + +#include "base/component_export.h" + +#define MEDIA_PARSERS_EXPORT COMPONENT_EXPORT(MEDIA_PARSER) + +#endif // MEDIA_PARSERS_MEDIA_PARSERS_EXPORT_H_ diff --git a/chromium/media/parsers/vp8_bool_decoder.cc b/chromium/media/parsers/vp8_bool_decoder.cc new file mode 100644 index 00000000000..4f156ad8daa --- /dev/null +++ b/chromium/media/parsers/vp8_bool_decoder.cc @@ -0,0 +1,209 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +/* + * Copyright (c) 2010, The WebM Project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google, nor the WebM Project, nor the names + * of its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This file is modified from the dboolhuff.{c,h} from the WebM's libvpx +// project. (http://www.webmproject.org/code) +// It is used to decode bits from a vp8 stream. + +#include "media/parsers/vp8_bool_decoder.h" + +#include <limits.h> + +#include <algorithm> + +#include "base/numerics/safe_conversions.h" + +namespace media { + +#define VP8_BD_VALUE_BIT \ + static_cast<int>(sizeof(Vp8BoolDecoder::value_) * CHAR_BIT) + +static const int kDefaultProbability = 0x80; // 0x80 / 256 = 0.5 + +// This is meant to be a large, positive constant that can still be efficiently +// loaded as an immediate (on platforms like ARM, for example). Even relatively +// modest values like 100 would work fine. +#define VP8_LOTS_OF_BITS (0x40000000) + +// The number of leading zeros. +static const unsigned char kVp8Norm[256] = { + 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +Vp8BoolDecoder::Vp8BoolDecoder() + : user_buffer_(NULL), + user_buffer_end_(NULL), + value_(0), + count_(-8), + range_(255) { +} + +bool Vp8BoolDecoder::Initialize(const uint8_t* data, size_t size) { + if (data == NULL || size == 0) + return false; + user_buffer_start_ = data; + user_buffer_ = data; + user_buffer_end_ = data + size; + value_ = 0; + count_ = -8; + range_ = 255; + return true; +} + +void Vp8BoolDecoder::FillDecoder() { + DCHECK(user_buffer_ != NULL); + int shift = VP8_BD_VALUE_BIT - CHAR_BIT - (count_ + CHAR_BIT); + size_t bytes_left = user_buffer_end_ - user_buffer_; + size_t bits_left = bytes_left * CHAR_BIT; + int x = static_cast<int>(shift + CHAR_BIT - bits_left); + int loop_end = 0; + + if (x >= 0) { + count_ += VP8_LOTS_OF_BITS; + loop_end = x; + } + + if (x < 0 || bits_left) { + while (shift >= loop_end) { + count_ += CHAR_BIT; + value_ |= static_cast<size_t>(*user_buffer_) << shift; + ++user_buffer_; + shift -= CHAR_BIT; + } + } +} + +int Vp8BoolDecoder::ReadBit(int probability) { + int bit = 0; + size_t split = 1 + (((range_ - 1) * probability) >> 8); + if (count_ < 0) + FillDecoder(); + size_t bigsplit = static_cast<size_t>(split) << (VP8_BD_VALUE_BIT - 8); + + if (value_ >= bigsplit) { + range_ -= split; + value_ -= bigsplit; + bit = 1; + } else { + range_ = split; + } + + size_t shift = kVp8Norm[range_]; + range_ <<= shift; + value_ <<= shift; + count_ -= shift; + + DCHECK_EQ(1U, (range_ >> 7)); // In the range [128, 255]. + + return bit; +} + +bool Vp8BoolDecoder::ReadLiteral(size_t num_bits, int* out) { + DCHECK_LE(num_bits, sizeof(int) * CHAR_BIT); + *out = 0; + for (; num_bits > 0; --num_bits) + *out = (*out << 1) | ReadBit(kDefaultProbability); + return !OutOfBuffer(); +} + +bool Vp8BoolDecoder::ReadBool(bool* out, uint8_t probability) { + *out = !!ReadBit(probability); + return !OutOfBuffer(); +} + +bool Vp8BoolDecoder::ReadBool(bool* out) { + return ReadBool(out, kDefaultProbability); +} + +bool Vp8BoolDecoder::ReadLiteralWithSign(size_t num_bits, int* out) { + ReadLiteral(num_bits, out); + // Read sign. + if (ReadBit(kDefaultProbability)) + *out = -*out; + return !OutOfBuffer(); +} + +size_t Vp8BoolDecoder::BitOffset() { + int bit_count = count_ + 8; + if (bit_count > VP8_BD_VALUE_BIT) + // Capped at 0 to ignore buffer underrun. + bit_count = std::max(0, bit_count - VP8_LOTS_OF_BITS); + return (user_buffer_ - user_buffer_start_) * 8 - bit_count; +} + +uint8_t Vp8BoolDecoder::GetRange() { + return base::checked_cast<uint8_t>(range_); +} + +uint8_t Vp8BoolDecoder::GetBottom() { + if (count_ < 0) + FillDecoder(); + return static_cast<uint8_t>(value_ >> (VP8_BD_VALUE_BIT - 8)); +} + +inline bool Vp8BoolDecoder::OutOfBuffer() { + // Check if we have reached the end of the buffer. + // + // Variable |count_| stores the number of bits in the |value_| buffer, minus + // 8. The top byte is part of the algorithm and the remainder is buffered to + // be shifted into it. So, if |count_| == 8, the top 16 bits of |value_| are + // occupied, 8 for the algorithm and 8 in the buffer. + // + // When reading a byte from the user's buffer, |count_| is filled with 8 and + // one byte is filled into the |value_| buffer. When we reach the end of the + // data, |count_| is additionally filled with VP8_LOTS_OF_BITS. So when + // |count_| == VP8_LOTS_OF_BITS - 1, the user's data has been exhausted. + return (count_ > VP8_BD_VALUE_BIT) && (count_ < VP8_LOTS_OF_BITS); +} + +} // namespace media diff --git a/chromium/media/parsers/vp8_bool_decoder.h b/chromium/media/parsers/vp8_bool_decoder.h new file mode 100644 index 00000000000..0f407cfbcc9 --- /dev/null +++ b/chromium/media/parsers/vp8_bool_decoder.h @@ -0,0 +1,135 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// + +/* + * Copyright (c) 2010, The WebM Project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google, nor the WebM Project, nor the names + * of its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This file is modified from the dboolhuff.{c,h} from the WebM's libvpx +// project. (http://www.webmproject.org/code) +// It is used to decode bits from a vp8 stream. + +#ifndef MEDIA_PARSERS_VP8_BOOL_DECODER_H_ +#define MEDIA_PARSERS_VP8_BOOL_DECODER_H_ + +#include <stddef.h> +#include <stdint.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "media/parsers/media_parsers_export.h" + +namespace media { + +// A class to decode the VP8's boolean entropy coded stream. It's a variant of +// arithmetic coding. See RFC 6386 - Chapter 7. Boolean Entropy Decoder. +class MEDIA_PARSERS_EXPORT Vp8BoolDecoder { + public: + Vp8BoolDecoder(); + + // Initializes the decoder to start decoding |data|, |size| being size + // of |data| in bytes. Returns false if |data| is NULL or empty. + bool Initialize(const uint8_t* data, size_t size); + + // Reads a boolean from the coded stream. Returns false if it has reached the + // end of |data| and failed to read the boolean. The probability of |out| to + // be true is |probability| / 256, e.g., when |probability| is 0x80, the + // chance is 1/2 (i.e., 0x80 / 256). + bool ReadBool(bool* out, uint8_t probability); + + // Reads a boolean from the coded stream with the default probability 1/2. + // Returns false if it has reached the end of |data| and failed to read the + // boolean. + bool ReadBool(bool* out); + + // Reads a "literal", that is, a "num_bits"-wide unsigned value whose bits + // come high- to low-order, with each bit encoded at probability 1/2. + // Returns false if it has reached the end of |data| and failed to read the + // literal. + bool ReadLiteral(size_t num_bits, int* out); + + // Reads a literal with sign from the coded stream. This is similar to + // the ReadListeral(), it first read a "num_bits"-wide unsigned value, and + // then read an extra bit as the sign of the literal. Returns false if it has + // reached the end of |data| and failed to read the literal or the sign. + // This is different from the "read_signed_literal(d, n)" defined in RFC 6386. + bool ReadLiteralWithSign(size_t num_bits, int* out); + + // The following methods are used to get the internal states of the decoder. + + // Returns the bit offset to the current top bit of the coded stream. It is + // also the number of bits that have been written in the corresponding + // encoding state. More specifically, we have the following constraint: + // w + (bottom * S) <= v < w + (bottom + range) * S, + // where "w" is for the bits already written, + // "v" is for the possible values of the coded number. + // "S" is the scale for the current bit position, + // i.e., S = pow(2, -(n + 8)), where "n" is the bit number of "w". + // BitOffset() returns the bit count of "w", i.e., "n". + size_t BitOffset(); + + // Gets the "bottom" of the current coded value. See BitOffset() for + // more details. + uint8_t GetBottom(); + + // Gets the "range" of the current coded value. See BitOffset() for + // more details. + uint8_t GetRange(); + + private: + // Reads the next bit from the coded stream. The probability of the bit to + // be one is |probability| / 256. + int ReadBit(int probability); + + // Fills more bits from |user_buffer_| to |value_|. We shall keep at least 8 + // bits of the current |user_buffer_| in |value_|. + void FillDecoder(); + + // Returns true iff we have ran out of bits. + bool OutOfBuffer(); + + const uint8_t* user_buffer_; + const uint8_t* user_buffer_start_; + const uint8_t* user_buffer_end_; + size_t value_; + int count_; + size_t range_; + + DISALLOW_COPY_AND_ASSIGN(Vp8BoolDecoder); +}; + +} // namespace media + +#endif // MEDIA_PARSERS_VP8_BOOL_DECODER_H_ diff --git a/chromium/media/parsers/vp8_bool_decoder_unittest.cc b/chromium/media/parsers/vp8_bool_decoder_unittest.cc new file mode 100644 index 00000000000..87968f4fc26 --- /dev/null +++ b/chromium/media/parsers/vp8_bool_decoder_unittest.cc @@ -0,0 +1,126 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/parsers/vp8_bool_decoder.h" + +#include <stddef.h> +#include <stdint.h> + +#include <limits> + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +const static size_t NUM_BITS_TO_TEST = 100; + +namespace { + +// 100 zeros with probability of 0x80. +const uint8_t kDataZerosAndEvenProbabilities[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + +// 100 ones with probability of 0x80. +const uint8_t kDataOnesAndEvenProbabilities[] = { + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf0, 0x20}; + +// [0, 1, 0, 1, ..., 1] with probability [0, 1, 2, 3, ..., 99]. +const uint8_t kDataParitiesAndIncreasingProbabilities[] = { + 0x00, 0x02, 0x08, 0x31, 0x8e, 0xca, 0xab, 0xe2, 0xc8, 0x31, 0x12, + 0xb3, 0x2c, 0x19, 0x90, 0xc6, 0x6a, 0xeb, 0x17, 0x52, 0x30}; + +} // namespace + +class Vp8BoolDecoderTest : public ::testing::Test { + public: + Vp8BoolDecoderTest() = default; + + protected: + // Fixture member, the bool decoder to be tested. + Vp8BoolDecoder bd_; + + private: + DISALLOW_COPY_AND_ASSIGN(Vp8BoolDecoderTest); +}; + +#define INITIALIZE(data) ASSERT_TRUE(bd_.Initialize(data, sizeof(data))); + +TEST_F(Vp8BoolDecoderTest, DecodeBoolsWithZerosAndEvenProbabilities) { + INITIALIZE(kDataZerosAndEvenProbabilities); + ASSERT_EQ(0u, bd_.BitOffset()); + for (size_t i = 0; i < NUM_BITS_TO_TEST; ++i) { + bool out = true; + ASSERT_TRUE(bd_.ReadBool(&out, 0x80)); + ASSERT_FALSE(out); + ASSERT_EQ(i, bd_.BitOffset()); + } +} + +TEST_F(Vp8BoolDecoderTest, DecodeLiteralsWithZerosAndEvenProbabilities) { + INITIALIZE(kDataZerosAndEvenProbabilities); + + int value = 1; + ASSERT_TRUE(bd_.ReadLiteral(1, &value)); + ASSERT_EQ(0, value); + + value = 1; + ASSERT_TRUE(bd_.ReadLiteral(32, &value)); + ASSERT_EQ(0, value); + + value = 1; + ASSERT_TRUE(bd_.ReadLiteralWithSign(1, &value)); + ASSERT_EQ(0, value); + + value = 1; + ASSERT_TRUE(bd_.ReadLiteralWithSign(31, &value)); + ASSERT_EQ(0, value); +} + +TEST_F(Vp8BoolDecoderTest, DecodeBoolsWithOnesAndEvenProbabilities) { + INITIALIZE(kDataOnesAndEvenProbabilities); + + ASSERT_EQ(0u, bd_.BitOffset()); + for (size_t i = 0; i < NUM_BITS_TO_TEST; ++i) { + bool out = false; + ASSERT_TRUE(bd_.ReadBool(&out, 0x80)); + ASSERT_TRUE(out); + ASSERT_EQ(i + 1, bd_.BitOffset()); + } +} + +TEST_F(Vp8BoolDecoderTest, DecodeLiteralsWithOnesAndEvenProbabilities) { + INITIALIZE(kDataOnesAndEvenProbabilities); + + int value = 0; + ASSERT_TRUE(bd_.ReadLiteral(1, &value)); + EXPECT_EQ(1, value); + + value = 0; + ASSERT_TRUE(bd_.ReadLiteral(31, &value)); + EXPECT_EQ(0x7FFFFFFF, value); + + value = 0; + ASSERT_TRUE(bd_.ReadLiteralWithSign(1, &value)); + EXPECT_EQ(-1, value); + + value = 0; + ASSERT_TRUE(bd_.ReadLiteralWithSign(31, &value)); + EXPECT_EQ(-0x7FFFFFFF, value); +} + +TEST_F(Vp8BoolDecoderTest, DecodeBoolsWithParitiesAndIncreasingProbabilities) { + INITIALIZE(kDataParitiesAndIncreasingProbabilities); + + for (size_t i = 0; i < NUM_BITS_TO_TEST; ++i) { + bool out = !(i & 1); + ASSERT_LE(i, std::numeric_limits<uint8_t>::max()); + ASSERT_TRUE(bd_.ReadBool(&out, static_cast<uint8_t>(i))); + EXPECT_EQ(out, !!(i & 1)); + } +} + +} // namespace media diff --git a/chromium/media/parsers/vp8_parser.cc b/chromium/media/parsers/vp8_parser.cc new file mode 100644 index 00000000000..04345b5f110 --- /dev/null +++ b/chromium/media/parsers/vp8_parser.cc @@ -0,0 +1,876 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains an implementation of a VP8 raw stream parser, +// as defined in RFC 6386. + +#include "media/parsers/vp8_parser.h" + +#include "base/logging.h" + +namespace media { + +#define ERROR_RETURN(what) \ + do { \ + DVLOG(1) << "Error while trying to read " #what; \ + return false; \ + } while (0) + +#define BD_READ_BOOL_OR_RETURN(out) \ + do { \ + if (!bd_.ReadBool(out)) \ + ERROR_RETURN(out); \ + } while (0) + +#define BD_READ_BOOL_WITH_PROB_OR_RETURN(out, prob) \ + do { \ + if (!bd_.ReadBool(out, prob)) \ + ERROR_RETURN(out); \ + } while (0) + +#define BD_READ_UNSIGNED_OR_RETURN(num_bits, out) \ + do { \ + int _out; \ + if (!bd_.ReadLiteral(num_bits, &_out)) \ + ERROR_RETURN(out); \ + *out = _out; \ + } while (0) + +#define BD_READ_SIGNED_OR_RETURN(num_bits, out) \ + do { \ + int _out; \ + if (!bd_.ReadLiteralWithSign(num_bits, &_out)) \ + ERROR_RETURN(out); \ + *out = _out; \ + } while (0) + +Vp8FrameHeader::Vp8FrameHeader() { + memset(this, 0, sizeof(*this)); +} + +Vp8Parser::Vp8Parser() : stream_(nullptr), bytes_left_(0) { +} + +Vp8Parser::~Vp8Parser() = default; + +bool Vp8Parser::ParseFrame(const uint8_t* ptr, + size_t frame_size, + Vp8FrameHeader* fhdr) { + stream_ = ptr; + bytes_left_ = frame_size; + + memset(fhdr, 0, sizeof(*fhdr)); + fhdr->data = stream_; + fhdr->frame_size = bytes_left_; + + if (!ParseFrameTag(fhdr)) + return false; + + fhdr->first_part_offset = stream_ - fhdr->data; + + if (!ParseFrameHeader(fhdr)) + return false; + + if (!ParsePartitions(fhdr)) + return false; + + DVLOG(4) << "Frame parsed, start: " << static_cast<const void*>(ptr) + << ", size: " << frame_size + << ", offsets: to first_part=" << fhdr->first_part_offset + << ", to macroblock data (in bits)=" << fhdr->macroblock_bit_offset; + + return true; +} + +static inline uint32_t GetBitsAt(uint32_t data, size_t shift, size_t num_bits) { + return ((data >> shift) & ((1 << num_bits) - 1)); +} + +bool Vp8Parser::ParseFrameTag(Vp8FrameHeader* fhdr) { + const size_t kFrameTagSize = 3; + if (bytes_left_ < kFrameTagSize) + return false; + + uint32_t frame_tag = (stream_[2] << 16) | (stream_[1] << 8) | stream_[0]; + fhdr->frame_type = + static_cast<Vp8FrameHeader::FrameType>(GetBitsAt(frame_tag, 0, 1)); + fhdr->version = GetBitsAt(frame_tag, 1, 2); + fhdr->is_experimental = !!GetBitsAt(frame_tag, 3, 1); + fhdr->show_frame =!!GetBitsAt(frame_tag, 4, 1); + fhdr->first_part_size = GetBitsAt(frame_tag, 5, 19); + + stream_ += kFrameTagSize; + bytes_left_ -= kFrameTagSize; + + if (fhdr->IsKeyframe()) { + const size_t kKeyframeTagSize = 7; + if (bytes_left_ < kKeyframeTagSize) + return false; + + static const uint8_t kVp8StartCode[] = {0x9d, 0x01, 0x2a}; + if (memcmp(stream_, kVp8StartCode, sizeof(kVp8StartCode)) != 0) + return false; + + stream_ += sizeof(kVp8StartCode); + bytes_left_ -= sizeof(kVp8StartCode); + + uint16_t data = (stream_[1] << 8) | stream_[0]; + fhdr->width = data & 0x3fff; + fhdr->horizontal_scale = data >> 14; + + data = (stream_[3] << 8) | stream_[2]; + fhdr->height = data & 0x3fff; + fhdr->vertical_scale = data >> 14; + + stream_ += 4; + bytes_left_ -= 4; + } + + return true; +} + +bool Vp8Parser::ParseFrameHeader(Vp8FrameHeader* fhdr) { + if (!bd_.Initialize(stream_, bytes_left_)) + return false; + + bool keyframe = fhdr->IsKeyframe(); + if (keyframe) { + unsigned int data; + BD_READ_UNSIGNED_OR_RETURN(1, &data); // color_space + BD_READ_UNSIGNED_OR_RETURN(1, &data); // clamping_type + } + + if (!ParseSegmentationHeader(keyframe)) + return false; + + fhdr->segmentation_hdr = curr_segmentation_hdr_; + + if (!ParseLoopFilterHeader(keyframe)) + return false; + + fhdr->loopfilter_hdr = curr_loopfilter_hdr_; + + int log2_nbr_of_dct_partitions; + BD_READ_UNSIGNED_OR_RETURN(2, &log2_nbr_of_dct_partitions); + fhdr->num_of_dct_partitions = static_cast<size_t>(1) + << log2_nbr_of_dct_partitions; + + if (!ParseQuantizationHeader(&fhdr->quantization_hdr)) + return false; + + if (keyframe) { + BD_READ_BOOL_OR_RETURN(&fhdr->refresh_entropy_probs); + } else { + BD_READ_BOOL_OR_RETURN(&fhdr->refresh_golden_frame); + BD_READ_BOOL_OR_RETURN(&fhdr->refresh_alternate_frame); + + int refresh_mode; + if (!fhdr->refresh_golden_frame) { + BD_READ_UNSIGNED_OR_RETURN(2, &refresh_mode); + fhdr->copy_buffer_to_golden = + static_cast<Vp8FrameHeader::GoldenRefreshMode>(refresh_mode); + } + + if (!fhdr->refresh_alternate_frame) { + BD_READ_UNSIGNED_OR_RETURN(2, &refresh_mode); + fhdr->copy_buffer_to_alternate = + static_cast<Vp8FrameHeader::AltRefreshMode>(refresh_mode); + } + + BD_READ_UNSIGNED_OR_RETURN(1, &fhdr->sign_bias_golden); + BD_READ_UNSIGNED_OR_RETURN(1, &fhdr->sign_bias_alternate); + BD_READ_BOOL_OR_RETURN(&fhdr->refresh_entropy_probs); + BD_READ_BOOL_OR_RETURN(&fhdr->refresh_last); + } + + if (keyframe) + ResetProbs(); + + fhdr->entropy_hdr = curr_entropy_hdr_; + + if (!ParseTokenProbs(&fhdr->entropy_hdr, fhdr->refresh_entropy_probs)) + return false; + + BD_READ_BOOL_OR_RETURN(&fhdr->mb_no_skip_coeff); + if (fhdr->mb_no_skip_coeff) + BD_READ_UNSIGNED_OR_RETURN(8, &fhdr->prob_skip_false); + + if (!keyframe) { + BD_READ_UNSIGNED_OR_RETURN(8, &fhdr->prob_intra); + BD_READ_UNSIGNED_OR_RETURN(8, &fhdr->prob_last); + BD_READ_UNSIGNED_OR_RETURN(8, &fhdr->prob_gf); + } + + if (!ParseIntraProbs(&fhdr->entropy_hdr, fhdr->refresh_entropy_probs, + keyframe)) + return false; + + if (!keyframe) { + if (!ParseMVProbs(&fhdr->entropy_hdr, fhdr->refresh_entropy_probs)) + return false; + } + + fhdr->macroblock_bit_offset = bd_.BitOffset(); + fhdr->bool_dec_range = bd_.GetRange(); + fhdr->bool_dec_value = bd_.GetBottom(); + fhdr->bool_dec_count = 7 - (bd_.BitOffset() + 7) % 8; + + return true; +} + +bool Vp8Parser::ParseSegmentationHeader(bool keyframe) { + Vp8SegmentationHeader* shdr = &curr_segmentation_hdr_; + + if (keyframe) + memset(shdr, 0, sizeof(*shdr)); + + BD_READ_BOOL_OR_RETURN(&shdr->segmentation_enabled); + if (!shdr->segmentation_enabled) + return true; + + BD_READ_BOOL_OR_RETURN(&shdr->update_mb_segmentation_map); + BD_READ_BOOL_OR_RETURN(&shdr->update_segment_feature_data); + if (shdr->update_segment_feature_data) { + int mode; + BD_READ_UNSIGNED_OR_RETURN(1, &mode); + shdr->segment_feature_mode = + static_cast<Vp8SegmentationHeader::SegmentFeatureMode>(mode); + + for (size_t i = 0; i < kMaxMBSegments; ++i) { + bool quantizer_update; + BD_READ_BOOL_OR_RETURN(&quantizer_update); + if (quantizer_update) + BD_READ_SIGNED_OR_RETURN(7, &shdr->quantizer_update_value[i]); + else + shdr->quantizer_update_value[i] = 0; + } + + for (size_t i = 0; i < kMaxMBSegments; ++i) { + bool loop_filter_update; + BD_READ_BOOL_OR_RETURN(&loop_filter_update); + if (loop_filter_update) + BD_READ_SIGNED_OR_RETURN(6, &shdr->lf_update_value[i]); + else + shdr->lf_update_value[i] = 0; + } + } + + if (shdr->update_mb_segmentation_map) { + for (size_t i = 0; i < kNumMBFeatureTreeProbs; ++i) { + bool segment_prob_update; + BD_READ_BOOL_OR_RETURN(&segment_prob_update); + if (segment_prob_update) + BD_READ_UNSIGNED_OR_RETURN(8, &shdr->segment_prob[i]); + else + shdr->segment_prob[i] = Vp8SegmentationHeader::kDefaultSegmentProb; + } + } + + return true; +} + +bool Vp8Parser::ParseLoopFilterHeader(bool keyframe) { + Vp8LoopFilterHeader* lfhdr = &curr_loopfilter_hdr_; + + if (keyframe) + memset(lfhdr, 0, sizeof(*lfhdr)); + + int type; + BD_READ_UNSIGNED_OR_RETURN(1, &type); + lfhdr->type = static_cast<Vp8LoopFilterHeader::Type>(type); + BD_READ_UNSIGNED_OR_RETURN(6, &lfhdr->level); + BD_READ_UNSIGNED_OR_RETURN(3, &lfhdr->sharpness_level); + BD_READ_BOOL_OR_RETURN(&lfhdr->loop_filter_adj_enable); + + if (lfhdr->loop_filter_adj_enable) { + BD_READ_BOOL_OR_RETURN(&lfhdr->mode_ref_lf_delta_update); + if (lfhdr->mode_ref_lf_delta_update) { + for (size_t i = 0; i < kNumBlockContexts; ++i) { + bool ref_frame_delta_update_flag; + BD_READ_BOOL_OR_RETURN(&ref_frame_delta_update_flag); + if (ref_frame_delta_update_flag) + BD_READ_SIGNED_OR_RETURN(6, &lfhdr->ref_frame_delta[i]); + } + + for (size_t i = 0; i < kNumBlockContexts; ++i) { + bool mb_mode_delta_update_flag; + BD_READ_BOOL_OR_RETURN(&mb_mode_delta_update_flag); + if (mb_mode_delta_update_flag) + BD_READ_SIGNED_OR_RETURN(6, &lfhdr->mb_mode_delta[i]); + } + } + } + + return true; +} + +bool Vp8Parser::ParseQuantizationHeader(Vp8QuantizationHeader* qhdr) { + // If any of the delta values is not present, the delta should be zero. + memset(qhdr, 0, sizeof(*qhdr)); + + BD_READ_UNSIGNED_OR_RETURN(7, &qhdr->y_ac_qi); + + bool delta_present; + + BD_READ_BOOL_OR_RETURN(&delta_present); + if (delta_present) + BD_READ_SIGNED_OR_RETURN(4, &qhdr->y_dc_delta); + + BD_READ_BOOL_OR_RETURN(&delta_present); + if (delta_present) + BD_READ_SIGNED_OR_RETURN(4, &qhdr->y2_dc_delta); + + BD_READ_BOOL_OR_RETURN(&delta_present); + if (delta_present) + BD_READ_SIGNED_OR_RETURN(4, &qhdr->y2_ac_delta); + + BD_READ_BOOL_OR_RETURN(&delta_present); + if (delta_present) + BD_READ_SIGNED_OR_RETURN(4, &qhdr->uv_dc_delta); + + BD_READ_BOOL_OR_RETURN(&delta_present); + if (delta_present) + BD_READ_SIGNED_OR_RETURN(4, &qhdr->uv_ac_delta); + + return true; +} + +// See spec for details on these values. +const uint8_t kCoeffUpdateProbs[kNumBlockTypes][kNumCoeffBands] + [kNumPrevCoeffContexts][kNumEntropyNodes] = { + { + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255}, + {249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255}, + {234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255}, + {250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + { + { + {217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255}, + {234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255}, + }, + { + {255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + { + { + {186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255}, + {234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255}, + {251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255}, + }, + { + {255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + { + { + {248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255}, + {248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255}, + {248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255}, + {250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + { + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, +}; + +const uint8_t kKeyframeYModeProbs[kNumYModeProbs] = {145, 156, 163, 128}; +const uint8_t kKeyframeUVModeProbs[kNumUVModeProbs] = {142, 114, 183}; + +const uint8_t kDefaultYModeProbs[kNumYModeProbs] = {112, 86, 140, 37}; +const uint8_t kDefaultUVModeProbs[kNumUVModeProbs] = {162, 101, 204}; + +const uint8_t kDefaultCoeffProbs[kNumBlockTypes][kNumCoeffBands] + [kNumPrevCoeffContexts][kNumEntropyNodes] = { + { + { + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + {253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128}, + {189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128}, + {106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128}, + }, + { + { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128}, + {181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128}, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128}, + }, + { + { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128}, + {184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128}, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128}, + }, + { + { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128}, + {170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128}, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128}, + }, + { + { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128}, + {207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128}, + {102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128}, + }, + { + { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128}, + {177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128}, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128}, + }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + } + }, + { + { + {198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62}, + {131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1}, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128}, + }, + { + { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128}, + {184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128}, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128}, + }, + { + { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128}, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128}, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128}, + }, + { + { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128}, + {109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128}, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128}, + }, + { + { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128}, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128}, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128}, + }, + { + { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128}, + {124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128}, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128}, + }, + { + { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128}, + {121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128}, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128}, + }, + { + { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128}, + {203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128}, + {137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128}, + } + }, + { + { + {253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128}, + {175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128}, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128}, + }, + { + { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128}, + {239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128}, + {155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128}, + }, + { + { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128}, + {201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128}, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128}, + }, + { + { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128}, + {223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128}, + {141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128}, + }, + { + { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128}, + {190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128}, + {149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128}, + {213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128}, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + { + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, + } + }, + { + { + {202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255}, + {126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128}, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128}, + }, + { + { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128}, + {166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128}, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128}, + }, + { + { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128}, + {124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128}, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128}, + }, + { + { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128}, + {149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128}, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128} + }, + { + { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128}, + {123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128}, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128}, + }, + { + { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128}, + {168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128}, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128}, + }, + { + { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128}, + {141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128}, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128}, + }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + {238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128}, + }, + }, +}; + +const uint8_t kMVUpdateProbs[kNumMVContexts][kNumMVProbs] = +{ + { + 237, 246, 253, 253, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 250, 250, 252, 254, 254, + }, + { + 231, 243, 245, 253, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 251, 251, 254, 254, 254, + }, +}; + +const uint8_t kDefaultMVProbs[kNumMVContexts][kNumMVProbs] = { + { + 162, 128, 225, 146, 172, 147, 214, 39, 156, + 128, 129, 132, 75, 145, 178, 206, 239, 254, 254, + }, + { + 164, 128, 204, 170, 119, 235, 140, 230, 228, + 128, 130, 130, 74, 148, 180, 203, 236, 254, 254, + }, +}; + +void Vp8Parser::ResetProbs() { + static_assert( + sizeof(curr_entropy_hdr_.coeff_probs) == sizeof(kDefaultCoeffProbs), + "coeff_probs_arrays_must_be_of_correct_size"); + memcpy(curr_entropy_hdr_.coeff_probs, kDefaultCoeffProbs, + sizeof(curr_entropy_hdr_.coeff_probs)); + + static_assert(sizeof(curr_entropy_hdr_.mv_probs) == sizeof(kDefaultMVProbs), + "mv_probs_arrays_must_be_of_correct_size"); + memcpy(curr_entropy_hdr_.mv_probs, kDefaultMVProbs, + sizeof(curr_entropy_hdr_.mv_probs)); + + static_assert( + sizeof(curr_entropy_hdr_.y_mode_probs) == sizeof(kDefaultYModeProbs), + "y_probs_arrays_must_be_of_correct_size"); + memcpy(curr_entropy_hdr_.y_mode_probs, kDefaultYModeProbs, + sizeof(curr_entropy_hdr_.y_mode_probs)); + + static_assert( + sizeof(curr_entropy_hdr_.uv_mode_probs) == sizeof(kDefaultUVModeProbs), + "uv_probs_arrays_must_be_of_correct_size"); + memcpy(curr_entropy_hdr_.uv_mode_probs, kDefaultUVModeProbs, + sizeof(curr_entropy_hdr_.uv_mode_probs)); +} + +bool Vp8Parser::ParseTokenProbs(Vp8EntropyHeader* ehdr, + bool update_curr_probs) { + for (size_t i = 0; i < kNumBlockTypes; ++i) { + for (size_t j = 0; j < kNumCoeffBands; ++j) { + for (size_t k = 0; k < kNumPrevCoeffContexts; ++k) { + for (size_t l = 0; l < kNumEntropyNodes; ++l) { + bool coeff_prob_update_flag; + BD_READ_BOOL_WITH_PROB_OR_RETURN(&coeff_prob_update_flag, + kCoeffUpdateProbs[i][j][k][l]); + if (coeff_prob_update_flag) + BD_READ_UNSIGNED_OR_RETURN(8, &ehdr->coeff_probs[i][j][k][l]); + } + } + } + } + + if (update_curr_probs) { + memcpy(curr_entropy_hdr_.coeff_probs, ehdr->coeff_probs, + sizeof(curr_entropy_hdr_.coeff_probs)); + } + + return true; +} + +bool Vp8Parser::ParseIntraProbs(Vp8EntropyHeader* ehdr, + bool update_curr_probs, + bool keyframe) { + if (keyframe) { + static_assert( + sizeof(ehdr->y_mode_probs) == sizeof(kKeyframeYModeProbs), + "y_probs_arrays_must_be_of_correct_size"); + memcpy(ehdr->y_mode_probs, kKeyframeYModeProbs, + sizeof(ehdr->y_mode_probs)); + + static_assert( + sizeof(ehdr->uv_mode_probs) == sizeof(kKeyframeUVModeProbs), + "uv_probs_arrays_must_be_of_correct_size"); + memcpy(ehdr->uv_mode_probs, kKeyframeUVModeProbs, + sizeof(ehdr->uv_mode_probs)); + } else { + bool intra_16x16_prob_update_flag; + BD_READ_BOOL_OR_RETURN(&intra_16x16_prob_update_flag); + if (intra_16x16_prob_update_flag) { + for (size_t i = 0; i < kNumYModeProbs; ++i) + BD_READ_UNSIGNED_OR_RETURN(8, &ehdr->y_mode_probs[i]); + + if (update_curr_probs) { + memcpy(curr_entropy_hdr_.y_mode_probs, ehdr->y_mode_probs, + sizeof(curr_entropy_hdr_.y_mode_probs)); + } + } + + bool intra_chroma_prob_update_flag; + BD_READ_BOOL_OR_RETURN(&intra_chroma_prob_update_flag); + if (intra_chroma_prob_update_flag) { + for (size_t i = 0; i < kNumUVModeProbs; ++i) + BD_READ_UNSIGNED_OR_RETURN(8, &ehdr->uv_mode_probs[i]); + + if (update_curr_probs) { + memcpy(curr_entropy_hdr_.uv_mode_probs, ehdr->uv_mode_probs, + sizeof(curr_entropy_hdr_.uv_mode_probs)); + } + } + } + + return true; +} + +bool Vp8Parser::ParseMVProbs(Vp8EntropyHeader* ehdr, bool update_curr_probs) { + for (size_t mv_ctx = 0; mv_ctx < kNumMVContexts; ++mv_ctx) { + for (size_t p = 0; p < kNumMVProbs; ++p) { + bool mv_prob_update_flag; + BD_READ_BOOL_WITH_PROB_OR_RETURN(&mv_prob_update_flag, + kMVUpdateProbs[mv_ctx][p]); + if (mv_prob_update_flag) { + uint8_t prob; + BD_READ_UNSIGNED_OR_RETURN(7, &prob); + ehdr->mv_probs[mv_ctx][p] = prob ? (prob << 1) : 1; + } + } + } + + if (update_curr_probs) { + memcpy(curr_entropy_hdr_.mv_probs, ehdr->mv_probs, + sizeof(curr_entropy_hdr_.mv_probs)); + } + + return true; +} + +bool Vp8Parser::ParsePartitions(Vp8FrameHeader* fhdr) { + CHECK_GE(fhdr->num_of_dct_partitions, 1u); + CHECK_LE(fhdr->num_of_dct_partitions, kMaxDCTPartitions); + + // DCT partitions start after the first partition and partition size values + // that follow it. There are num_of_dct_partitions - 1 sizes stored in the + // stream after the first partition, each 3 bytes long. The size of last + // DCT partition is not stored in the stream, but is instead calculated by + // taking the remainder of the frame size after the penultimate DCT partition. + size_t first_dct_pos = fhdr->first_part_offset + fhdr->first_part_size + + (fhdr->num_of_dct_partitions - 1) * 3; + + // Make sure we have enough data for the first partition and partition sizes. + if (fhdr->frame_size < first_dct_pos) + return false; + + // Total size of all DCT partitions. + size_t bytes_left = fhdr->frame_size - first_dct_pos; + + // Position ourselves at the beginning of partition size values. + const uint8_t* ptr = + fhdr->data + fhdr->first_part_offset + fhdr->first_part_size; + + // Read sizes from the stream (if present). + for (size_t i = 0; i < fhdr->num_of_dct_partitions - 1; ++i) { + fhdr->dct_partition_sizes[i] = (ptr[2] << 16) | (ptr[1] << 8) | ptr[0]; + + // Make sure we have enough data in the stream for ith partition and + // subtract its size from total. + if (bytes_left < fhdr->dct_partition_sizes[i]) + return false; + + bytes_left -= fhdr->dct_partition_sizes[i]; + + // Move to the position of the next partition size value. + ptr += 3; + } + + // The remainder of the data belongs to the last DCT partition. + fhdr->dct_partition_sizes[fhdr->num_of_dct_partitions - 1] = bytes_left; + + DVLOG(4) << "Control part size: " << fhdr->first_part_size; + for (size_t i = 0; i < fhdr->num_of_dct_partitions; ++i) + DVLOG(4) << "DCT part " << i << " size: " << fhdr->dct_partition_sizes[i]; + + return true; +} + +} // namespace media diff --git a/chromium/media/parsers/vp8_parser.h b/chromium/media/parsers/vp8_parser.h new file mode 100644 index 00000000000..11585bc04e1 --- /dev/null +++ b/chromium/media/parsers/vp8_parser.h @@ -0,0 +1,208 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains an implementation of a VP8 raw stream parser, +// as defined in RFC 6386. + +#ifndef MEDIA_PARSERS_VP8_PARSER_H_ +#define MEDIA_PARSERS_VP8_PARSER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "media/parsers/media_parsers_export.h" +#include "media/parsers/vp8_bool_decoder.h" + +namespace media { + +// See spec for definitions of values/fields. +const size_t kMaxMBSegments = 4; +const size_t kNumMBFeatureTreeProbs = 3; + +// Member of Vp8FrameHeader and will be 0-initialized +// in Vp8FrameHeader's constructor. +struct Vp8SegmentationHeader { + enum SegmentFeatureMode { FEATURE_MODE_DELTA = 0, FEATURE_MODE_ABSOLUTE = 1 }; + + bool segmentation_enabled; + bool update_mb_segmentation_map; + bool update_segment_feature_data; + SegmentFeatureMode segment_feature_mode; + + int8_t quantizer_update_value[kMaxMBSegments]; + int8_t lf_update_value[kMaxMBSegments]; + static const int kDefaultSegmentProb = 255; + uint8_t segment_prob[kNumMBFeatureTreeProbs]; +}; + +const size_t kNumBlockContexts = 4; + +// Member of Vp8FrameHeader and will be 0-initialized +// in Vp8FrameHeader's constructor. +struct Vp8LoopFilterHeader { + enum Type { LOOP_FILTER_TYPE_NORMAL = 0, LOOP_FILTER_TYPE_SIMPLE = 1 }; + Type type; + uint8_t level; + uint8_t sharpness_level; + bool loop_filter_adj_enable; + bool mode_ref_lf_delta_update; + + int8_t ref_frame_delta[kNumBlockContexts]; + int8_t mb_mode_delta[kNumBlockContexts]; +}; + +// Member of Vp8FrameHeader and will be 0-initialized +// in Vp8FrameHeader's constructor. +struct Vp8QuantizationHeader { + uint8_t y_ac_qi; + int8_t y_dc_delta; + int8_t y2_dc_delta; + int8_t y2_ac_delta; + int8_t uv_dc_delta; + int8_t uv_ac_delta; +}; + +const size_t kNumBlockTypes = 4; +const size_t kNumCoeffBands = 8; +const size_t kNumPrevCoeffContexts = 3; +const size_t kNumEntropyNodes = 11; + +const size_t kNumMVContexts = 2; +const size_t kNumMVProbs = 19; + +const size_t kNumYModeProbs = 4; +const size_t kNumUVModeProbs = 3; + +// Member of Vp8FrameHeader and will be 0-initialized +// in Vp8FrameHeader's constructor. +struct Vp8EntropyHeader { + uint8_t coeff_probs[kNumBlockTypes][kNumCoeffBands][kNumPrevCoeffContexts] + [kNumEntropyNodes]; + + uint8_t y_mode_probs[kNumYModeProbs]; + uint8_t uv_mode_probs[kNumUVModeProbs]; + + uint8_t mv_probs[kNumMVContexts][kNumMVProbs]; +}; + +const size_t kMaxDCTPartitions = 8; +const size_t kNumVp8ReferenceBuffers = 3; + +enum Vp8RefType : size_t { + VP8_FRAME_LAST = 0, + VP8_FRAME_GOLDEN = 1, + VP8_FRAME_ALTREF = 2, +}; + +struct MEDIA_PARSERS_EXPORT Vp8FrameHeader { + Vp8FrameHeader(); + + enum FrameType { KEYFRAME = 0, INTERFRAME = 1 }; + bool IsKeyframe() const { return frame_type == KEYFRAME; } + + enum GoldenRefreshMode { + NO_GOLDEN_REFRESH = 0, + COPY_LAST_TO_GOLDEN = 1, + COPY_ALT_TO_GOLDEN = 2, + }; + + enum AltRefreshMode { + NO_ALT_REFRESH = 0, + COPY_LAST_TO_ALT = 1, + COPY_GOLDEN_TO_ALT = 2, + }; + + FrameType frame_type; + uint8_t version; + bool is_experimental; + bool show_frame; + size_t first_part_size; + + uint16_t width; + uint8_t horizontal_scale; + uint16_t height; + uint8_t vertical_scale; + + Vp8SegmentationHeader segmentation_hdr; + Vp8LoopFilterHeader loopfilter_hdr; + Vp8QuantizationHeader quantization_hdr; + + size_t num_of_dct_partitions; + + Vp8EntropyHeader entropy_hdr; + + bool refresh_entropy_probs; + bool refresh_golden_frame; + bool refresh_alternate_frame; + GoldenRefreshMode copy_buffer_to_golden; + AltRefreshMode copy_buffer_to_alternate; + uint8_t sign_bias_golden; + uint8_t sign_bias_alternate; + bool refresh_last; + + bool mb_no_skip_coeff; + uint8_t prob_skip_false; + uint8_t prob_intra; + uint8_t prob_last; + uint8_t prob_gf; + + const uint8_t* data; + size_t frame_size; + + size_t dct_partition_sizes[kMaxDCTPartitions]; + // Offset in bytes from data. + off_t first_part_offset; + // Offset in bits from first_part_offset. + off_t macroblock_bit_offset; + + // Bool decoder state + uint8_t bool_dec_range; + uint8_t bool_dec_value; + uint8_t bool_dec_count; +}; + +// A parser for raw VP8 streams as specified in RFC 6386. +class MEDIA_PARSERS_EXPORT Vp8Parser { + public: + Vp8Parser(); + ~Vp8Parser(); + + // Try to parse exactly one VP8 frame starting at |ptr| and of size |size|, + // filling the parsed data in |fhdr|. Return true on success. + // Size has to be exactly the size of the frame and coming from the caller, + // who needs to acquire it from elsewhere (normally from a container). + bool ParseFrame(const uint8_t* ptr, size_t size, Vp8FrameHeader* fhdr); + + private: + bool ParseFrameTag(Vp8FrameHeader* fhdr); + bool ParseFrameHeader(Vp8FrameHeader* fhdr); + + bool ParseSegmentationHeader(bool keyframe); + bool ParseLoopFilterHeader(bool keyframe); + bool ParseQuantizationHeader(Vp8QuantizationHeader* qhdr); + bool ParseTokenProbs(Vp8EntropyHeader* ehdr, bool update_curr_probs); + bool ParseIntraProbs(Vp8EntropyHeader* ehdr, + bool update_curr_probs, + bool keyframe); + bool ParseMVProbs(Vp8EntropyHeader* ehdr, bool update_curr_probs); + bool ParsePartitions(Vp8FrameHeader* fhdr); + void ResetProbs(); + + // These persist across calls to ParseFrame() and may be used and/or updated + // for subsequent frames if the stream instructs us to do so. + Vp8SegmentationHeader curr_segmentation_hdr_; + Vp8LoopFilterHeader curr_loopfilter_hdr_; + Vp8EntropyHeader curr_entropy_hdr_; + + const uint8_t* stream_; + size_t bytes_left_; + Vp8BoolDecoder bd_; + + DISALLOW_COPY_AND_ASSIGN(Vp8Parser); +}; + +} // namespace media + +#endif // MEDIA_PARSERS_VP8_PARSER_H_ diff --git a/chromium/media/parsers/vp8_parser_fuzzertest.cc b/chromium/media/parsers/vp8_parser_fuzzertest.cc new file mode 100644 index 00000000000..75cc6bb317b --- /dev/null +++ b/chromium/media/parsers/vp8_parser_fuzzertest.cc @@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "base/numerics/safe_conversions.h" +#include "media/filters/ivf_parser.h" +#include "media/parsers/vp8_parser.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const uint8_t* ivf_payload = nullptr; + media::IvfParser ivf_parser; + media::IvfFileHeader ivf_file_header; + media::IvfFrameHeader ivf_frame_header; + + if (!ivf_parser.Initialize(data, size, &ivf_file_header)) + return 0; + + // Parse until the end of stream/unsupported stream/error in stream is found. + while (ivf_parser.ParseNextFrame(&ivf_frame_header, &ivf_payload)) { + media::Vp8Parser vp8_parser; + media::Vp8FrameHeader vp8_frame_header; + vp8_parser.ParseFrame(ivf_payload, ivf_frame_header.frame_size, + &vp8_frame_header); + } + + return 0; +} diff --git a/chromium/media/parsers/vp8_parser_unittest.cc b/chromium/media/parsers/vp8_parser_unittest.cc new file mode 100644 index 00000000000..cbe4ff07b5f --- /dev/null +++ b/chromium/media/parsers/vp8_parser_unittest.cc @@ -0,0 +1,50 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" +#include "media/base/test_data_util.h" +#include "media/filters/ivf_parser.h" +#include "media/parsers/vp8_parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +TEST(Vp8ParserTest, StreamFileParsing) { + base::FilePath file_path = GetTestDataFilePath("test-25fps.vp8"); + base::MemoryMappedFile stream; + ASSERT_TRUE(stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + + IvfParser ivf_parser; + IvfFileHeader ivf_file_header = {}; + ASSERT_TRUE( + ivf_parser.Initialize(stream.data(), stream.length(), &ivf_file_header)); + ASSERT_EQ(ivf_file_header.fourcc, 0x30385056u); // VP80 + + Vp8Parser vp8_parser; + IvfFrameHeader ivf_frame_header = {}; + size_t num_parsed_frames = 0; + + // Parse until the end of stream/unsupported stream/error in stream is found. + const uint8_t* payload = nullptr; + while (ivf_parser.ParseNextFrame(&ivf_frame_header, &payload)) { + Vp8FrameHeader fhdr; + + ASSERT_TRUE( + vp8_parser.ParseFrame(payload, ivf_frame_header.frame_size, &fhdr)); + + ++num_parsed_frames; + } + + DVLOG(1) << "Number of successfully parsed frames before EOS: " + << num_parsed_frames; + + EXPECT_EQ(ivf_file_header.num_frames, num_parsed_frames); +} + +} // namespace media diff --git a/chromium/media/parsers/webp_parser.cc b/chromium/media/parsers/webp_parser.cc new file mode 100644 index 00000000000..b0348037e79 --- /dev/null +++ b/chromium/media/parsers/webp_parser.cc @@ -0,0 +1,131 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/parsers/webp_parser.h" + +#include <limits.h> +#include <stddef.h> +#include <string.h> + +#include "base/bits.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "build/build_config.h" +#include "media/parsers/vp8_parser.h" + +#if !defined(ARCH_CPU_LITTLE_ENDIAN) +#error Big-Endian architecture not supported. +#endif + +namespace media { + +namespace { + +// The byte position storing the size of the file. +constexpr size_t kFileSizeBytePosition = 4u; + +// The byte position in which the WebP image data begins. +constexpr size_t kWebPFileBeginBytePosition = 8u; + +// The byte position storing the size of the VP8 frame. +constexpr size_t kVp8FrameSizePosition = 16u; + +// The 12 bytes that include the FourCC "WEBPVP8 " plus the VP8 chunk size info. +constexpr size_t kWebPFileHeaderByteSize = 12u; + +// A valid WebP image header and VP8 chunk header require 20 bytes. +// The VP8 Key Frame's payload also begins at byte 20. +constexpr size_t kWebPFileAndVp8ChunkHeaderSizeInBytes = 20u; + +// The max WebP file size is (2^32 - 10) per the WebP spec: +// https://developers.google.com/speed/webp/docs/riff_container#webp_file_header +constexpr uint32_t kMaxWebPFileSize = (1ull << 32) - 10u; + +constexpr size_t kSizeOfUint32t = sizeof(uint32_t); + +} // namespace + +bool IsLossyWebPImage(base::span<const uint8_t> encoded_data) { + if (encoded_data.size() < kWebPFileAndVp8ChunkHeaderSizeInBytes) + return false; + + DCHECK(encoded_data.data()); + + return !memcmp(encoded_data.data(), "RIFF", 4) && + !memcmp(encoded_data.data() + kWebPFileBeginBytePosition, "WEBPVP8 ", + 8); +} + +std::unique_ptr<Vp8FrameHeader> ParseWebPImage( + base::span<const uint8_t> encoded_data) { + if (!IsLossyWebPImage(encoded_data)) + return nullptr; + + static_assert(CHAR_BIT == 8, "Size of a char is not 8 bits."); + static_assert(kSizeOfUint32t == 4u, "Size of uint32_t is not 4 bytes."); + + // Try to acquire the WebP file size. IsLossyWebPImage() has ensured + // that we have enough data to read the file size. + DCHECK_GE(encoded_data.size(), kFileSizeBytePosition + kSizeOfUint32t); + + // No need to worry about endianness because we assert little-endianness. + const uint32_t file_size = *reinterpret_cast<const uint32_t*>( + encoded_data.data() + kFileSizeBytePosition); + + // Check that |file_size| is even, per the WebP spec: + // https://developers.google.com/speed/webp/docs/riff_container#webp_file_header + if (file_size % 2 != 0) + return nullptr; + + // Check that |file_size| <= 2^32 - 10, per the WebP spec: + // https://developers.google.com/speed/webp/docs/riff_container#webp_file_header + if (file_size > kMaxWebPFileSize) + return nullptr; + + // Check that the file size in the header matches the encoded data's size. + if (base::strict_cast<size_t>(file_size) != + encoded_data.size() - kWebPFileBeginBytePosition) { + return nullptr; + } + + // Try to acquire the VP8 key frame size and validate that it fits within the + // encoded data's size. + DCHECK_GE(encoded_data.size(), kVp8FrameSizePosition + kSizeOfUint32t); + + const uint32_t vp8_frame_size = *reinterpret_cast<const uint32_t*>( + encoded_data.data() + kVp8FrameSizePosition); + + // Check that the VP8 frame size is bounded by the WebP size. + if (base::strict_cast<size_t>(file_size) - kWebPFileHeaderByteSize < + base::strict_cast<size_t>(vp8_frame_size)) { + return nullptr; + } + + // Check that the size of the encoded data is consistent. + const size_t vp8_padded_frame_size = + base::bits::Align(base::strict_cast<size_t>(vp8_frame_size), 2u); + if (encoded_data.size() - kWebPFileAndVp8ChunkHeaderSizeInBytes != + vp8_padded_frame_size) { + return nullptr; + } + + // Check that the last byte is 0 if |vp8_frame_size| is odd per WebP specs: + // https://developers.google.com/speed/webp/docs/riff_container#riff_file_format + if (vp8_frame_size % 2 && + encoded_data.data()[encoded_data.size() - 1] != 0u) { + return nullptr; + } + + // Attempt to parse the VP8 frame. + Vp8Parser vp8_parser; + auto result = std::make_unique<Vp8FrameHeader>(); + if (vp8_parser.ParseFrame( + encoded_data.data() + kWebPFileAndVp8ChunkHeaderSizeInBytes, + base::strict_cast<size_t>(vp8_frame_size), result.get())) { + return result; + } + return nullptr; +} + +} // namespace media diff --git a/chromium/media/parsers/webp_parser.h b/chromium/media/parsers/webp_parser.h new file mode 100644 index 00000000000..436d6c74210 --- /dev/null +++ b/chromium/media/parsers/webp_parser.h @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_PARSERS_WEBP_PARSER_H_ +#define MEDIA_PARSERS_WEBP_PARSER_H_ + +#include <stdint.h> +#include <memory> + +#include "base/containers/span.h" +#include "media/parsers/media_parsers_export.h" + +namespace media { + +struct Vp8FrameHeader; + +// A lightweight WebP file header parser to extract feature and size +// information. It validates that a given data stream encodes a simple lossy +// WebP image and populates a Vp8FrameHeader upon successful parsing. +// For more information, see the WebP Container Specification: +// https://developers.google.com/speed/webp/docs/riff_container + +// Returns true if |encoded_data| claims to encode a simple (non-extended) lossy +// WebP image. Returns false otherwise. +MEDIA_PARSERS_EXPORT +bool IsLossyWebPImage(base::span<const uint8_t> encoded_data); + +// Parses a simple (non-extended) lossy WebP image and returns a Vp8FrameHeader +// containing the parsed VP8 frame contained by the image. Returns nullptr on +// failure. +MEDIA_PARSERS_EXPORT +std::unique_ptr<Vp8FrameHeader> ParseWebPImage( + base::span<const uint8_t> encoded_data); + +} // namespace media + +#endif // MEDIA_PARSERS_WEBP_PARSER_H_ diff --git a/chromium/media/parsers/webp_parser_fuzzertest.cc b/chromium/media/parsers/webp_parser_fuzzertest.cc new file mode 100644 index 00000000000..d10c830c0af --- /dev/null +++ b/chromium/media/parsers/webp_parser_fuzzertest.cc @@ -0,0 +1,24 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "base/containers/span.h" +#include "base/logging.h" +#include "media/parsers/vp8_parser.h" +#include "media/parsers/webp_parser.h" + +struct Environment { + Environment() { logging::SetMinLogLevel(logging::LOG_FATAL); } +}; + +Environment* env = new Environment(); + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + base::span<const uint8_t> encoded_data(data, size); + media::ParseWebPImage(encoded_data); + return 0; +} diff --git a/chromium/media/parsers/webp_parser_unittest.cc b/chromium/media/parsers/webp_parser_unittest.cc new file mode 100644 index 00000000000..4c9d99765b1 --- /dev/null +++ b/chromium/media/parsers/webp_parser_unittest.cc @@ -0,0 +1,327 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "base/base_paths.h" +#include "base/containers/span.h" +#include "base/files/file_path.h" +#include "base/files/memory_mapped_file.h" +#include "base/path_service.h" +#include "media/parsers/vp8_parser.h" +#include "media/parsers/webp_parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +namespace { + +constexpr size_t kWebPFileAndVp8ChunkHeaderSizeInBytes = 20u; +// clang-format off +constexpr uint8_t kLossyWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0x0c, 0x00, 0x00, 0x00, // == 12 (little endian) + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x00, 0x00, 0x00, 0x00 // == 0 +}; +constexpr base::span<const uint8_t> kLossyWebPEncodedData( + kLossyWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); +constexpr base::span<const uint8_t> kInvalidWebPEncodedDataSize( + kLossyWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes - 5u); + +constexpr uint8_t kLosslessWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0x0c, 0x00, 0x00, 0x00, + 'W', 'E', 'B', 'P', + 'V', 'P', '8', 'L', + 0x00, 0x00, 0x00, 0x00 +}; +constexpr base::span<const uint8_t> kLosslessWebPEncodedData( + kLosslessWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); + +constexpr uint8_t kExtendedWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0x0c, 0x00, 0x00, 0x00, + 'W', 'E', 'B', 'P', + 'V', 'P', '8', 'X', + 0x00, 0x00, 0x00, 0x00 +}; +constexpr base::span<const uint8_t> kExtendedWebPEncodedData( + kExtendedWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); + +constexpr uint8_t kUnknownWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0x0c, 0x00, 0x00, 0x00, + 'W', 'E', 'B', 'P', + 'V', 'P', '8', '~', + 0x00, 0x00, 0x00, 0x00 +}; +constexpr base::span<const uint8_t> kUnknownWebPEncodedData( + kUnknownWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); + +constexpr uint8_t kInvalidRiffWebPFileHeader[] = { + 'X', 'I', 'F', 'F', + 0x0c, 0x00, 0x00, 0x00, + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x00, 0x00, 0x00, 0x00 +}; +constexpr base::span<const uint8_t> kInvalidRiffWebPEncodedData( + kInvalidRiffWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); + +constexpr uint8_t kInvalidOddFileSizeInWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0x0d, 0x00, 0x00, 0x00, // == 13 (Invalid: should be even) + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x00, 0x00, 0x00, 0x00, + 0x00 +}; +constexpr base::span<const uint8_t> kInvalidOddFileSizeInHeaderWebPEncodedData( + kInvalidOddFileSizeInWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes + 1u); // Match the reported size + +constexpr uint8_t kInvalidLargerThanLimitFileSizeInWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0xfe, 0xff, 0xff, 0xff, // == 2^32 - 2 (Invalid: should be <= 2^32 - 10) + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x00, 0x00, 0x00, 0x00 +}; +constexpr base::span<const uint8_t> +kInvalidLargerThanLimitFileSizeInHeaderWebPEncodedData( + kInvalidLargerThanLimitFileSizeInWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); + +constexpr uint8_t kInvalidLargerFileSizeInWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0x10, 0x00, 0x00, 0x00, // == 16 (Invalid: should be 12) + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x00, 0x00, 0x00, 0x00 +}; +constexpr base::span<const uint8_t> +kInvalidLargerFileSizeInHeaderWebPEncodedData( + kInvalidLargerFileSizeInWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); + +constexpr uint8_t kInvalidKeyFrameSizeInWebPFileHeader[] = { + 'R', 'I', 'F', 'F', + 0x0c, 0x00, 0x00, 0x00, // == 12 + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0xc8, 0x00, 0x00, 0x00 // == 200 (Invalid: should be 0) +}; +constexpr base::span<const uint8_t> kInvalidKeyFrameSizeInWebPEncodedData( + kInvalidKeyFrameSizeInWebPFileHeader, + kWebPFileAndVp8ChunkHeaderSizeInBytes); + +constexpr uint8_t kMismatchingOddVp8FrameSizeAndDataSize[] = { + 'R', 'I', 'F', 'F', + 0x12, 0x00, 0x00, 0x00, // == 18 + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x03, 0x00, 0x00, 0x00, // == 3 + 0x11, 0xa0, 0x23, 0x00, // Valid padding byte + 0xfa, 0xcc // Should not exist. +}; +constexpr base::span<const uint8_t> +kMismatchingOddVp8FrameSizeAndDataSizeEncodedData( + kMismatchingOddVp8FrameSizeAndDataSize, + kWebPFileAndVp8ChunkHeaderSizeInBytes + 6u); + +constexpr uint8_t kMismatchingEvenVp8FrameSizeAndDataSize[] = { + 'R', 'I', 'F', 'F', + 0x12, 0x00, 0x00, 0x00, // == 18 + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x04, 0x00, 0x00, 0x00, // == 4 + 0x11, 0xa0, 0x23, 0x12, + 0xfc, 0xcd // Should not exist. +}; +constexpr base::span<const uint8_t> +kMismatchingEvenVp8FrameSizeAndDataSizeEncodedData( + kMismatchingEvenVp8FrameSizeAndDataSize, + kWebPFileAndVp8ChunkHeaderSizeInBytes + 6u); + +constexpr uint8_t kInvalidPaddingByteInVp8DataChunk[] = { + 'R', 'I', 'F', 'F', + 0x10, 0x00, 0x00, 0x00, // == 16 + 'W', 'E', 'B', 'P', + 'V', 'P', '8', ' ', + 0x03, 0x00, 0x00, 0x00, // == 3 + 0x11, 0xa0, 0x23, 0xff // Invalid: last byte should be 0 +}; +constexpr base::span<const uint8_t> +kInvalidPaddingByteInVp8DataChunkEncodedData( + kInvalidPaddingByteInVp8DataChunk, + kWebPFileAndVp8ChunkHeaderSizeInBytes + 4u); +// clang-format on + +} // namespace + +TEST(WebPParserTest, WebPImageFileValidator) { + // Verify that only lossy WebP formats pass. + ASSERT_TRUE(IsLossyWebPImage(kLossyWebPEncodedData)); + + // Verify that lossless, extended, and unknown WebP formats fail. + ASSERT_FALSE(IsLossyWebPImage(kLosslessWebPEncodedData)); + ASSERT_FALSE(IsLossyWebPImage(kExtendedWebPEncodedData)); + ASSERT_FALSE(IsLossyWebPImage(kUnknownWebPEncodedData)); + + // Verify that invalid WebP file headers and sizes fail. + ASSERT_FALSE(IsLossyWebPImage(kInvalidRiffWebPEncodedData)); + ASSERT_FALSE(IsLossyWebPImage(kInvalidWebPEncodedDataSize)); +} + +TEST(WebPParserTest, ParseLossyWebP) { + base::FilePath data_dir; + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + + base::FilePath file_path = data_dir.AppendASCII("media") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("red_green_gradient_lossy.webp"); + + base::MemoryMappedFile stream; + ASSERT_TRUE(stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(base::span<const uint8_t>(stream.data(), stream.length())); + ASSERT_TRUE(result); + + ASSERT_TRUE(result->IsKeyframe()); + ASSERT_TRUE(result->data); + + // Original image is 3000x3000. + ASSERT_EQ(3000u, result->width); + ASSERT_EQ(3000u, result->height); +} + +TEST(WebPParserTest, ParseLosslessWebP) { + base::FilePath data_dir; + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + + base::FilePath file_path = + data_dir.AppendASCII("media") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("yellow_pink_gradient_lossless.webp"); + + base::MemoryMappedFile stream; + ASSERT_TRUE(stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + + // Should fail because WebP parser does not parse lossless webp images. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(base::span<const uint8_t>(stream.data(), stream.length())); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseExtendedWebP) { + base::FilePath data_dir; + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir)); + + base::FilePath file_path = data_dir.AppendASCII("media") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("bouncy_ball.webp"); + + base::MemoryMappedFile stream; + ASSERT_TRUE(stream.Initialize(file_path)) + << "Couldn't open stream file: " << file_path.MaybeAsASCII(); + + // Should fail because WebP parser does not parse extended webp images. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(base::span<const uint8_t>(stream.data(), stream.length())); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithUnknownFormat) { + // Should fail when the specifier byte at position 16 holds anything but ' '. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kUnknownWebPEncodedData); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithInvalidHeaders) { + // Should fail because the header is an invalid WebP container. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kInvalidRiffWebPEncodedData); + ASSERT_FALSE(result); + + // Should fail because the header has an invalid size. + result = ParseWebPImage(kInvalidWebPEncodedDataSize); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithInvalidOddSizeInHeader) { + // Should fail because the size reported in the header is odd. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kInvalidOddFileSizeInHeaderWebPEncodedData); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithInvalidLargerThanLimitSizeInHeader) { + // Should fail because the size reported in the header is larger than + // 2^32 - 10 per the WebP spec. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kInvalidLargerThanLimitFileSizeInHeaderWebPEncodedData); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithInvalidFileSizeInHeader) { + // Should fail because the size reported in the header does not match the + // actual data size. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kInvalidLargerFileSizeInHeaderWebPEncodedData); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithEmptyVp8KeyFrameAndIncorrectKeyFrameSize) { + // Should fail because the reported VP8 key frame size is larger than the + // the existing data. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kInvalidKeyFrameSizeInWebPEncodedData); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithMismatchingVp8FrameAndDataSize) { + // Should fail because the reported VP8 key frame size (even or odd) does not + // match the encoded data's size. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kMismatchingOddVp8FrameSizeAndDataSizeEncodedData); + ASSERT_FALSE(result); + + result = ParseWebPImage(kMismatchingEvenVp8FrameSizeAndDataSizeEncodedData); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithInvalidPaddingByteInVp8DataChunk) { + // Should fail because the reported VP8 key frame size is odd and the added + // padding byte is not 0. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kInvalidPaddingByteInVp8DataChunkEncodedData); + ASSERT_FALSE(result); +} + +TEST(WebPParserTest, ParseWebPWithEmptyVp8KeyFrame) { + // Should fail because the VP8 parser is passed a data chunk of size 0. + std::unique_ptr<Vp8FrameHeader> result = + ParseWebPImage(kLossyWebPEncodedData); + ASSERT_FALSE(result); +} + +} // namespace media |