Fix: Obtain aom_image metadata on decode
Fixes reported issues oss-fuzz:19118, oss-fuzz:19157 and decoder
conformance test vector failure.
Summary:
- Added aom_img_get_metadata() function so user can obtain metadata
associated with an aom_image.
- Added aom_img_num_metadata() function so user can get the number of
metadata blocks associated with an aom_image.
- Added image metadata array to AV1Decoder so it can be obtained during
decoding process.
- Implemented read_metadata_itut_t35() to parse ITU-T T.35 metadata from
stream to metadata array.
- Added necessary tests for these new functions.
- Only ITU-T T.35 metadata decoding is implemented in this CL.
BUG=aomedia:2507
Change-Id: I33f32b3e59d25630bd7f5c3eb1791fc8ffee384c
diff --git a/aom/aom_image.h b/aom/aom_image.h
index 863277e..14603a6 100644
--- a/aom/aom_image.h
+++ b/aom/aom_image.h
@@ -30,7 +30,7 @@
* types, removing or reassigning enums, adding/removing/rearranging
* fields to structures
*/
-#define AOM_IMAGE_ABI_VERSION (6) /**<\hideinitializer*/
+#define AOM_IMAGE_ABI_VERSION (7) /**<\hideinitializer*/
#define AOM_IMG_FMT_PLANAR 0x100 /**< Image is a planar format. */
#define AOM_IMG_FMT_UV_FLIP 0x200 /**< V plane precedes U in memory. */
@@ -351,6 +351,33 @@
int aom_img_add_metadata(aom_image_t *img, uint32_t type, const uint8_t *data,
size_t sz);
+/*!\brief Return a metadata payload stored within the image metadata array.
+ *
+ * Gets the metadata (aom_metadata_t) at the indicated index in the image
+ * metadata array.
+ *
+ * \param[in] img Pointer to image descriptor to get metadata from
+ * \param[in] index Metadata index to get from metadata array
+ *
+ * \return Returns a const pointer to the selected metadata, if img and/or index
+ * is invalid, it returns NULL.
+ */
+const aom_metadata_t *aom_img_get_metadata(const aom_image_t *img,
+ size_t index);
+
+/*!\brief Return the number of metadata blocks within the image.
+ *
+ * Gets the number of metadata blocks contained within the provided image
+ * metadata array.
+ *
+ * \param[in] img Pointer to image descriptor to get metadata number
+ * from.
+ *
+ * \return Returns the size of the metadata array. If img or metadata is NULL,
+ * it returns 0.
+ */
+size_t aom_img_num_metadata(const aom_image_t *img);
+
/*!\brief Remove metadata from image.
*
* Removes all metadata in image metadata list and sets metadata list pointer
@@ -362,12 +389,9 @@
/*!\brief Allocate memory for aom_metadata struct.
*
- * Allocates memory for aom_metadata struct and sets its type. Optionally
- * allocates storage for the metadata payload and copies the payload data
- * into the aom_metadata struct:
- * - When sz is > 0 and data is NULL, allocates metadata payload buffer of sz.
- * - When sz is > 0 and data is non-NULL, a metadata payload buffer of sz
- * is allocated and sz bytes are copied from data into the payload buffer.
+ * Allocates storage for the metadata payload, sets its type and copies the
+ * payload data into the aom_metadata struct. A metadata payload buffer of size
+ * sz is allocated and sz bytes are copied from data into the payload buffer.
*
* \param[in] type Metadata type
* \param[in] data Metadata data pointer
diff --git a/aom/exports_com b/aom/exports_com
index a192cf9..68dbfe0 100644
--- a/aom/exports_com
+++ b/aom/exports_com
@@ -15,10 +15,12 @@
text aom_img_alloc_with_border
text aom_img_flip
text aom_img_free
+text aom_img_get_metadata
text aom_img_metadata_array_free
text aom_img_metadata_array_alloc
text aom_img_metadata_free
text aom_img_metadata_alloc
+text aom_img_num_metadata
text aom_img_plane_height
text aom_img_plane_width
text aom_img_remove_metadata
diff --git a/aom/src/aom_image.c b/aom/src/aom_image.c
index 5e2edb4..c8676cf 100644
--- a/aom/src/aom_image.c
+++ b/aom/src/aom_image.c
@@ -290,21 +290,17 @@
aom_metadata_t *aom_img_metadata_alloc(uint32_t type, const uint8_t *data,
size_t sz) {
- aom_metadata_t *metadata =
- (aom_metadata_t *)calloc(1, sizeof(aom_metadata_t));
+ if (!data || sz == 0) return NULL;
+ aom_metadata_t *metadata = (aom_metadata_t *)malloc(sizeof(aom_metadata_t));
if (!metadata) return NULL;
metadata->type = type;
- if (sz > 0) {
- metadata->payload = (uint8_t *)calloc(sz, sizeof(uint8_t));
- if (!metadata->payload) {
- free(metadata);
- return NULL;
- }
- if (data) {
- memcpy(metadata->payload, data, sz);
- metadata->sz = sz;
- }
+ metadata->payload = (uint8_t *)malloc(sz);
+ if (!metadata->payload) {
+ free(metadata);
+ return NULL;
}
+ memcpy(metadata->payload, data, sz);
+ metadata->sz = sz;
return metadata;
}
@@ -379,3 +375,18 @@
img->metadata = NULL;
}
}
+
+const aom_metadata_t *aom_img_get_metadata(const aom_image_t *img,
+ size_t index) {
+ if (!img) return NULL;
+ const aom_metadata_array_t *array = img->metadata;
+ if (array && index < array->sz) {
+ return array->metadata_array[index];
+ }
+ return NULL;
+}
+
+size_t aom_img_num_metadata(const aom_image_t *img) {
+ if (!img || !img->metadata) return 0;
+ return img->metadata->sz;
+}
diff --git a/aom_scale/generic/yv12config.c b/aom_scale/generic/yv12config.c
index bb7cae0..b007b7b 100644
--- a/aom_scale/generic/yv12config.c
+++ b/aom_scale/generic/yv12config.c
@@ -301,7 +301,7 @@
if (!ybf || !arr || !arr->metadata_array) return -1;
aom_remove_metadata_from_frame_buffer(ybf);
ybf->metadata = aom_img_metadata_array_alloc(arr->sz);
- if (!ybf->metadata) return 0;
+ if (!ybf->metadata) return -1;
for (size_t i = 0; i < ybf->metadata->sz; i++) {
ybf->metadata->metadata_array[i] = aom_img_metadata_alloc(
arr->metadata_array[i]->type, arr->metadata_array[i]->payload,
diff --git a/av1/av1_dx_iface.c b/av1/av1_dx_iface.c
index 6b06ad4..4a0a217 100644
--- a/av1/av1_dx_iface.c
+++ b/av1/av1_dx_iface.c
@@ -152,6 +152,7 @@
aom_free(ctx->frame_workers);
aom_free(ctx->buffer_pool);
+ aom_img_free(&ctx->img);
aom_free(ctx);
return AOM_CODEC_OK;
}
@@ -761,6 +762,15 @@
return grain_img;
}
+// Copies and clears the metadata from AV1Decoder.
+static void move_decoder_metadata_to_img(AV1Decoder *pbi, aom_image_t *img) {
+ if (pbi->metadata && img) {
+ assert(!img->metadata);
+ img->metadata = pbi->metadata;
+ pbi->metadata = NULL;
+ }
+}
+
static aom_image_t *decoder_get_frame(aom_codec_alg_priv_t *ctx,
aom_codec_iter_t *iter) {
aom_image_t *img = NULL;
@@ -800,12 +810,15 @@
RefCntBuffer *const output_frame_buf = pbi->output_frames[*index];
ctx->last_show_frame = output_frame_buf;
if (ctx->need_resync) return NULL;
+ aom_img_remove_metadata(&ctx->img);
yuvconfig2image(&ctx->img, sd, frame_worker_data->user_priv);
+ move_decoder_metadata_to_img(pbi, &ctx->img);
if (!pbi->ext_tile_debug && cm->large_scale_tile) {
*index += 1; // Advance the iterator to point to the next image
-
+ aom_img_remove_metadata(&ctx->img);
yuvconfig2image(&ctx->img, &pbi->tile_list_outbuf, NULL);
+ move_decoder_metadata_to_img(pbi, &ctx->img);
img = &ctx->img;
return img;
}
diff --git a/av1/av1_iface_common.h b/av1/av1_iface_common.h
index f87fd1b..9b5ffcb 100644
--- a/av1/av1_iface_common.h
+++ b/av1/av1_iface_common.h
@@ -11,6 +11,8 @@
#ifndef AOM_AV1_AV1_IFACE_COMMON_H_
#define AOM_AV1_AV1_IFACE_COMMON_H_
+#include <assert.h>
+
#include "aom_ports/mem.h"
#include "aom_scale/yv12config.h"
@@ -74,7 +76,8 @@
img->img_data_owner = 0;
img->self_allocd = 0;
img->sz = yv12->frame_size;
- img->metadata = yv12->metadata;
+ assert(!yv12->metadata);
+ img->metadata = NULL;
}
static aom_codec_err_t image2yuvconfig(const aom_image_t *img,
diff --git a/av1/decoder/decoder.c b/av1/decoder/decoder.c
index c2b84d4..574b30c 100644
--- a/av1/decoder/decoder.c
+++ b/av1/decoder/decoder.c
@@ -247,7 +247,7 @@
aom_accounting_clear(&pbi->accounting);
#endif
av1_free_mc_tmp_buf(&pbi->td);
-
+ aom_img_metadata_array_free(pbi->metadata);
aom_free(pbi);
}
diff --git a/av1/decoder/decoder.h b/av1/decoder/decoder.h
index 6df582d..25f2ac5 100644
--- a/av1/decoder/decoder.h
+++ b/av1/decoder/decoder.h
@@ -243,6 +243,7 @@
#endif
AV1DecRowMTInfo frame_row_mt_info;
+ aom_metadata_array_t *metadata;
} AV1Decoder;
// Returns 0 on success. Sets pbi->common.error.error_code to a nonzero error
diff --git a/av1/decoder/obu.c b/av1/decoder/obu.c
index c725db1..56ac72c 100644
--- a/av1/decoder/obu.c
+++ b/av1/decoder/obu.c
@@ -548,32 +548,60 @@
return tile_list_payload_size;
}
-// Reads the country code as specified in Recommendation ITU-T T.35. On
-// success, returns the number of bytes read from 'data'. On failure, calls
+// Returns the last nonzero byte index in 'data'. If there is no nonzero byte in
+// 'data', returns -1.
+static int get_last_nonzero_byte_index(const uint8_t *data, size_t sz) {
+ // Scan backward and return on the first nonzero byte.
+ int i = (int)sz - 1;
+ while (i >= 0 && data[i] == 0) {
+ --i;
+ }
+ return i;
+}
+
+// On success, returns the number of bytes read from 'data'. On failure, calls
// aom_internal_error() and does not return.
-//
-// Note: This function does not read itu_t_t35_payload_bytes because the exact
-// syntax of itu_t_t35_payload_bytes is not defined in the spec.
-static size_t read_metadata_itut_t35(AV1_COMMON *const cm, const uint8_t *data,
+static size_t read_metadata_itut_t35(AV1Decoder *const pbi, const uint8_t *data,
size_t sz) {
- size_t i = 0;
- // itu_t_t35_country_code f(8)
- if (i >= sz) {
+ AV1_COMMON *const cm = &pbi->common;
+ if (sz == 0) {
aom_internal_error(&cm->error, AOM_CODEC_CORRUPT_FRAME,
"itu_t_t35_country_code is missing");
}
- const int itu_t_t35_country_code = data[i];
- ++i;
- if (itu_t_t35_country_code == 0xFF) {
- // itu_t_t35_country_code_extension_byte f(8)
- if (i >= sz) {
- aom_internal_error(&cm->error, AOM_CODEC_CORRUPT_FRAME,
- "itu_t_t35_country_code_extension_byte is missing");
- }
- ++i;
+ if (*data == 0xFF && sz < 2) {
+ aom_internal_error(&cm->error, AOM_CODEC_CORRUPT_FRAME,
+ "itu_t_t35_country_code_extension_byte is missing");
}
- // itu_t_t35_payload_bytes
- return i;
+ int bytes_read = get_last_nonzero_byte_index(data, sz);
+ if (bytes_read < 0) {
+ aom_internal_error(&cm->error, AOM_CODEC_CORRUPT_FRAME,
+ "No trailing bits found on metadata");
+ }
+ aom_metadata_t *metadata = aom_img_metadata_alloc(OBU_METADATA_TYPE_ITUT_T35,
+ data, (size_t)bytes_read);
+ if (!metadata) {
+ aom_internal_error(&cm->error, AOM_CODEC_MEM_ERROR,
+ "Error allocating metadata");
+ }
+ if (!pbi->metadata) {
+ pbi->metadata = aom_img_metadata_array_alloc(1);
+ if (!pbi->metadata) {
+ aom_internal_error(&cm->error, AOM_CODEC_MEM_ERROR,
+ "Failed to allocate metadata array");
+ }
+ } else {
+ aom_metadata_t **metadata_array =
+ (aom_metadata_t **)realloc(pbi->metadata->metadata_array,
+ (pbi->metadata->sz + 1) * sizeof(metadata));
+ if (!metadata_array) {
+ aom_internal_error(&cm->error, AOM_CODEC_MEM_ERROR,
+ "Error allocating metadata");
+ }
+ pbi->metadata->metadata_array = metadata_array;
+ pbi->metadata->sz++;
+ }
+ pbi->metadata->metadata_array[pbi->metadata->sz - 1] = metadata;
+ return (size_t)bytes_read;
}
static void read_metadata_hdr_cll(struct aom_read_bit_buffer *rb) {
@@ -709,7 +737,7 @@
if (metadata_type == OBU_METADATA_TYPE_ITUT_T35) {
size_t bytes_read =
type_length +
- read_metadata_itut_t35(cm, data + type_length, sz - type_length);
+ read_metadata_itut_t35(pbi, data + type_length, sz - type_length);
// Ignore itu_t_t35_payload_bytes and check trailing bits. Section 6.7.2
// of the spec says:
// itu_t_t35_payload_bytes shall be bytes containing data registered as
diff --git a/test/metadata_test.cc b/test/metadata_test.cc
index bd8a1dd..6aeebdd 100644
--- a/test/metadata_test.cc
+++ b/test/metadata_test.cc
@@ -24,7 +24,9 @@
namespace {
const size_t kExampleDataSize = 10;
-const uint8_t kExampleData[kExampleDataSize] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+// 0xB5 stands for the itut t35 metadata country code for the Unites States
+const uint8_t kExampleData[kExampleDataSize] = { 0xB5, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08, 0x09 };
#if CONFIG_AV1_ENCODER
@@ -46,6 +48,21 @@
if (current_frame) {
if (current_frame->metadata) aom_img_remove_metadata(current_frame);
ASSERT_EQ(aom_img_add_metadata(current_frame, OBU_METADATA_TYPE_ITUT_T35,
+ kExampleData, 0),
+ -1);
+ ASSERT_EQ(aom_img_add_metadata(current_frame, OBU_METADATA_TYPE_ITUT_T35,
+ NULL, kExampleDataSize),
+ -1);
+ ASSERT_EQ(aom_img_add_metadata(current_frame, OBU_METADATA_TYPE_ITUT_T35,
+ NULL, 0),
+ -1);
+ ASSERT_EQ(aom_img_add_metadata(current_frame, OBU_METADATA_TYPE_ITUT_T35,
+ kExampleData, kExampleDataSize),
+ 0);
+
+ // The duplicate statement is deliberate to test multiple metadata objects
+ // per image.
+ ASSERT_EQ(aom_img_add_metadata(current_frame, OBU_METADATA_TYPE_ITUT_T35,
kExampleData, kExampleDataSize),
0);
}
@@ -54,23 +71,42 @@
virtual void FramePktHook(const aom_codec_cx_pkt_t *pkt) {
if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) {
const size_t metadata_obu_size = 14;
- const uint8_t metadata_obu[metadata_obu_size] = {
- 42, 12, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 128
- };
+ const uint8_t metadata_obu[metadata_obu_size] = { 0x2A, 0x0C, 0x04, 0xB5,
+ 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x80 };
const size_t bitstream_size = pkt->data.frame.sz;
const uint8_t *bitstream =
static_cast<const uint8_t *>(pkt->data.frame.buf);
- // look for expected metadata in bitstream
- bool found_metadata = false;
- for (size_t i = 0; i < bitstream_size; ++i) {
- if (memcmp(bitstream + i, metadata_obu, metadata_obu_size) == 0) {
- found_metadata = true;
- break;
+ // look for valid metadatas in bitstream
+ size_t valid_metadatas_found = 0;
+ if (bitstream_size >= metadata_obu_size) {
+ for (size_t i = 0; i <= bitstream_size - metadata_obu_size; ++i) {
+ if (memcmp(bitstream + i, metadata_obu, metadata_obu_size) == 0) {
+ valid_metadatas_found++;
+ }
}
}
- EXPECT_TRUE(found_metadata);
+ ASSERT_EQ(valid_metadatas_found, 2u);
}
}
+
+ virtual void DecompressedFrameHook(const aom_image_t &img,
+ aom_codec_pts_t /*pts*/) {
+ ASSERT_TRUE(img.metadata != nullptr);
+
+ ASSERT_EQ(img.metadata->sz, 2u);
+
+ ASSERT_EQ(kExampleDataSize, img.metadata->metadata_array[0]->sz);
+ EXPECT_EQ(memcmp(kExampleData, img.metadata->metadata_array[0]->payload,
+ kExampleDataSize),
+ 0);
+
+ ASSERT_EQ(kExampleDataSize, img.metadata->metadata_array[1]->sz);
+ EXPECT_EQ(memcmp(kExampleData, img.metadata->metadata_array[1]->payload,
+ kExampleDataSize),
+ 0);
+ }
};
TEST_P(MetadataEncodeTest, TestMetadataEncoding) {
@@ -181,3 +217,54 @@
aom_remove_metadata_from_frame_buffer(&yvBuf);
aom_remove_metadata_from_frame_buffer(NULL);
}
+
+TEST(MetadataTest, GetMetadataFromImage) {
+ aom_image_t image;
+ image.metadata = NULL;
+
+ ASSERT_EQ(aom_img_add_metadata(&image, OBU_METADATA_TYPE_ITUT_T35,
+ kExampleData, kExampleDataSize),
+ 0);
+
+ EXPECT_TRUE(aom_img_get_metadata(NULL, 0) == NULL);
+ EXPECT_TRUE(aom_img_get_metadata(&image, 1u) == NULL);
+ EXPECT_TRUE(aom_img_get_metadata(&image, 10u) == NULL);
+
+ const aom_metadata_t *metadata = aom_img_get_metadata(&image, 0);
+ ASSERT_TRUE(metadata != NULL);
+ ASSERT_EQ(metadata->sz, kExampleDataSize);
+ EXPECT_EQ(memcmp(kExampleData, metadata->payload, kExampleDataSize), 0);
+
+ aom_img_metadata_array_free(image.metadata);
+}
+
+TEST(MetadataTest, ReadMetadatasFromImage) {
+ aom_image_t image;
+ image.metadata = NULL;
+
+ uint32_t types[3];
+ types[0] = OBU_METADATA_TYPE_ITUT_T35;
+ types[1] = OBU_METADATA_TYPE_HDR_CLL;
+ types[2] = OBU_METADATA_TYPE_HDR_MDCV;
+
+ ASSERT_EQ(
+ aom_img_add_metadata(&image, types[0], kExampleData, kExampleDataSize),
+ 0);
+ ASSERT_EQ(
+ aom_img_add_metadata(&image, types[1], kExampleData, kExampleDataSize),
+ 0);
+ ASSERT_EQ(
+ aom_img_add_metadata(&image, types[2], kExampleData, kExampleDataSize),
+ 0);
+
+ size_t number_metadata = aom_img_num_metadata(&image);
+ ASSERT_EQ(number_metadata, 3u);
+ for (size_t i = 0; i < number_metadata; ++i) {
+ const aom_metadata_t *metadata = aom_img_get_metadata(&image, i);
+ ASSERT_TRUE(metadata != NULL);
+ ASSERT_EQ(metadata->type, types[i]);
+ ASSERT_EQ(metadata->sz, kExampleDataSize);
+ EXPECT_EQ(memcmp(kExampleData, metadata->payload, kExampleDataSize), 0);
+ }
+ aom_img_metadata_array_free(image.metadata);
+}