diff --git a/README.md b/README.md index ff6ca6db51..51cbbf1aa0 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ build/avmdec --all-layers --num-streams=2 -o decoded_%d.y4m stereo_muxed.obu ## Documentation -- **[Multi-XLayer Encoding Guide](doc/multi_xlayer_encoding.md)**: +- **[Multi-XLayer Encoding Guide](https://github.com/AOMediaCodec/avm/blob/multi-layer-av2/doc/multi_xlayer_encoding.md)**: full reference covering JSON schema, embedded layers, atlas, OPS, GOP modes, use cases, and constraints. diff --git a/apps/avmdec.c b/apps/avmdec.c index ee1600a777..19d90c89a6 100644 --- a/apps/avmdec.c +++ b/apps/avmdec.c @@ -706,15 +706,16 @@ static FILE *open_outfile(const char *name) { } // Dynamic composite groups derived from LCR layer properties. -// Each unique (layer_type, auxiliary_type, view_type) combination +// Each unique (layer_type, auxiliary_type, LcrViewId) combination // produces a separate composite output. Layers within a group must // share the same chroma format; mixed chroma forces separate outputs. // Mixed bit depth is handled by promoting to the highest bit depth. typedef struct CompositeGroup { - int layer_type; // TEXTURE_LAYER, AUX_LAYER, etc. - int auxiliary_type; // only meaningful when layer_type == AUX_LAYER - int view_type; // VIEW_UNSPECIFIED, VIEW_CENTER, VIEW_LEFT, etc. - int num_xlayers; // how many xlayers belong to this group + int layer_type; // TEXTURE_LAYER, AUX_LAYER, etc. + int auxiliary_type; // only meaningful when layer_type == AUX_LAYER + int derived_view_id; // LcrViewId: 0-3 = VIEW_UNSPECIFIED/CENTER/LEFT/ + // RIGHT, 256 + lcr_view_id for VIEW_EXPLICIT + int num_xlayers; // how many xlayers belong to this group int xlayer_ids[MAX_NUM_XLAYERS]; // xlayer_ids in this group int xlayer_indices[MAX_NUM_XLAYERS]; // indices into xlayer_cfg.xlayers[] // (-1 if from decoder query) @@ -736,12 +737,13 @@ static const char *comp_view_type_names[] = { "unspecified", "center", "left", // Build composite groups from arrays of per-xlayer properties. // Allocates comp_groups and fills *out_groups / *out_num_groups. -// xlayer_ids[], layer_types[], aux_types[], view_types[] are parallel arrays +// xlayer_ids[], layer_types[], aux_types[], derived_view_ids[] are parallel +// arrays (derived_view_ids[] holds LcrViewId values) // of length num_xlayers. config_indices[] provides the JSON config index for // each xlayer (-1 if built from decoder query). static void build_composite_groups(int num_xlayers, const int *xlayer_ids, const int *layer_types, const int *aux_types, - const int *view_types, + const int *derived_view_ids, const int *config_indices, CompositeGroup **out_groups, int *out_num_groups) { @@ -752,12 +754,12 @@ static void build_composite_groups(int num_xlayers, const int *xlayer_ids, for (int i = 0; i < num_xlayers; i++) { int lt = layer_types[i]; int at = aux_types[i]; - int vt = view_types[i]; + int derived_vid = derived_view_ids[i]; // Find existing group or create new int gidx = -1; for (int g = 0; g < num_groups; g++) { if (groups[g].layer_type == lt && groups[g].auxiliary_type == at && - groups[g].view_type == vt) { + groups[g].derived_view_id == derived_vid) { gidx = g; break; } @@ -766,7 +768,7 @@ static void build_composite_groups(int num_xlayers, const int *xlayer_ids, gidx = num_groups++; groups[gidx].layer_type = lt; groups[gidx].auxiliary_type = at; - groups[gidx].view_type = vt; + groups[gidx].derived_view_id = derived_vid; groups[gidx].num_xlayers = 0; groups[gidx].canvas = NULL; groups[gidx].outfile_cg = NULL; @@ -785,9 +787,19 @@ static void build_composite_groups(int num_xlayers, const int *xlayer_ids, const char *lt_name = (cg->layer_type >= 0 && cg->layer_type < 4) ? comp_layer_type_names[cg->layer_type] : "unknown"; - const char *vt_name = (cg->view_type >= 0 && cg->view_type < 5) - ? comp_view_type_names[cg->view_type] - : "unknown"; + // Non-explicit views are named by view type; explicit views by their + // lcr_view_id (LcrViewId - 256) so that different views get different + // labels (and therefore different output files). + char vt_name[32]; + if (cg->derived_view_id >= LCR_VIEW_ID_EXPLICIT_BASE) { + snprintf(vt_name, sizeof(vt_name), "view%d", + cg->derived_view_id - LCR_VIEW_ID_EXPLICIT_BASE); + } else if (cg->derived_view_id >= 0 && cg->derived_view_id < 5) { + snprintf(vt_name, sizeof(vt_name), "%s", + comp_view_type_names[cg->derived_view_id]); + } else { + snprintf(vt_name, sizeof(vt_name), "unknown"); + } if (cg->layer_type == AUX_LAYER && cg->auxiliary_type >= 0 && cg->auxiliary_type < 4) { snprintf(cg->label, sizeof(cg->label), "%s_%s_%s", @@ -1085,18 +1097,25 @@ static int main_loop(int argc, const char **argv_) { // Build composite groups eagerly from JSON config if (atlas_composite && xlayer_cfg.enable_atlas) { int xlids[MAX_NUM_XLAYERS], lts[MAX_NUM_XLAYERS]; - int ats[MAX_NUM_XLAYERS], vts[MAX_NUM_XLAYERS]; + int ats[MAX_NUM_XLAYERS], derived_vids[MAX_NUM_XLAYERS]; int idxs[MAX_NUM_XLAYERS]; for (int xi = 0; xi < xlayer_cfg.num_xlayers; xi++) { const XLayerEncConfig *xl = &xlayer_cfg.xlayers[xi]; xlids[xi] = xl->xlayer_id; lts[xi] = xl->layer_type; ats[xi] = (xl->layer_type == AUX_LAYER) ? xl->auxiliary_type : -1; - vts[xi] = xl->view_type; + const int view_id = xlayer_config_lcr_view_id(xl); + if (xl->view_type == VIEW_EXPLICIT && view_id < 0) { + die("Error: xlayer %d has view_type \"explicit\" but no valid " + "view_id (0-255)\n", + xl->xlayer_id); + } + derived_vids[xi] = av2_lcr_view_id(xl->view_type, view_id); idxs[xi] = xi; } - build_composite_groups(xlayer_cfg.num_xlayers, xlids, lts, ats, vts, idxs, - &comp_groups, &num_comp_groups); + build_composite_groups(xlayer_cfg.num_xlayers, xlids, lts, ats, + derived_vids, idxs, &comp_groups, + &num_comp_groups); comp_groups_built = 1; } } @@ -1384,15 +1403,16 @@ static int main_loop(int argc, const char **argv_) { &decoder, AV2D_GET_ATLAS_INFO, &dec_atlas_info); if (have_lcr && lcr_info.num_xlayers > 0) { - int xlids[31], lts[31], ats_arr[31], vts[31]; + int xlids[31], lts[31], ats_arr[31], derived_vids[31]; for (int li = 0; li < lcr_info.num_xlayers; li++) { xlids[li] = lcr_info.xlayers[li].xlayer_id; lts[li] = lcr_info.xlayers[li].layer_type; ats_arr[li] = lcr_info.xlayers[li].auxiliary_type; - vts[li] = lcr_info.xlayers[li].view_type; + derived_vids[li] = lcr_info.xlayers[li].derived_view_id; } - build_composite_groups(lcr_info.num_xlayers, xlids, lts, ats_arr, vts, - NULL, &comp_groups, &num_comp_groups); + build_composite_groups(lcr_info.num_xlayers, xlids, lts, ats_arr, + derived_vids, NULL, &comp_groups, + &num_comp_groups); comp_groups_built = 1; if (have_atlas && dec_atlas_info.num_segments > 0) { diff --git a/av2/av2_dx_iface.c b/av2/av2_dx_iface.c index 083d812e54..566f9b44d6 100644 --- a/av2/av2_dx_iface.c +++ b/av2/av2_dx_iface.c @@ -2049,11 +2049,13 @@ static avm_codec_err_t ctrl_get_lcr_info(avm_codec_alg_priv_t *ctx, xl->auxiliary_type = (ml->lcr_layer_type[0] == 1) ? ml->lcr_auxiliary_type[0] : -1; xl->view_type = ml->lcr_view_type[0]; + xl->derived_view_id = ml->LcrViewId[0]; } else { xl->num_mlayers = 0; xl->layer_type = 0; xl->auxiliary_type = -1; xl->view_type = 0; + xl->derived_view_id = 0; } } return AVM_CODEC_OK; @@ -2078,11 +2080,13 @@ static avm_codec_err_t ctrl_get_lcr_info(avm_codec_alg_priv_t *ctx, xl->auxiliary_type = (ml->lcr_layer_type[0] == 1) ? ml->lcr_auxiliary_type[0] : -1; xl->view_type = ml->lcr_view_type[0]; + xl->derived_view_id = ml->LcrViewId[0]; } else { xl->num_mlayers = 0; xl->layer_type = 0; xl->auxiliary_type = -1; xl->view_type = 0; + xl->derived_view_id = 0; } count++; break; // found LCR for this xlayer, move to next diff --git a/av2/common/av2_common_int.h b/av2/common/av2_common_int.h index 7108385821..2978aa2ce6 100644 --- a/av2/common/av2_common_int.h +++ b/av2/common/av2_common_int.h @@ -767,6 +767,8 @@ typedef struct EmbeddedLayerInfo { int lcr_auxiliary_type[MAX_NUM_MLAYERS]; int lcr_view_type[MAX_NUM_MLAYERS]; int lcr_view_id[MAX_NUM_MLAYERS]; + // Derived: av2_lcr_view_id(lcr_view_type, lcr_view_id) + int LcrViewId[MAX_NUM_MLAYERS]; int lcr_dependent_layer_map[MAX_NUM_MLAYERS]; int lcr_same_sh_max_resolution_flag[MAX_NUM_MLAYERS]; int lcr_max_expected_width[MAX_NUM_MLAYERS]; diff --git a/av2/common/enums.h b/av2/common/enums.h index 7a802dfb09..c1394f347f 100644 --- a/av2/common/enums.h +++ b/av2/common/enums.h @@ -355,10 +355,22 @@ enum { VIEW_LEFT = 2, VIEW_RIGHT = 3, VIEW_EXPLICIT = 4, - // Reserved: 5-7 + // Reserved: 5-255 VIEW_TYPES = 8 }; +// LcrViewId identifies the view of an embedded layer with a single value: +// 256 + lcr_view_id when lcr_view_type is VIEW_EXPLICIT (256..511), and +// lcr_view_type otherwise (0..255). lcr_view_id is only signalled for +// VIEW_EXPLICIT, so its value is not used for the other view types. +#define LCR_VIEW_ID_EXPLICIT_BASE 256 + +static AVM_INLINE int av2_lcr_view_id(int lcr_view_type, int lcr_view_id) { + return lcr_view_type == VIEW_EXPLICIT + ? LCR_VIEW_ID_EXPLICIT_BASE + lcr_view_id + : lcr_view_type; +} + enum { ENHANCED_ATLAS = 0, BASIC_ATLAS = 1, diff --git a/av2/decoder/obu.c b/av2/decoder/obu.c index d5afbf7914..b7b255ae09 100644 --- a/av2/decoder/obu.c +++ b/av2/decoder/obu.c @@ -1766,7 +1766,8 @@ int check_temporal_unit_structure(temporal_unit_state_t *state, int obu_type, // After the global phase, an xlayer_id change starts a fresh per-xlayer // group. No increasing-xlayer constraint: a TU may group all per-xlayer - // config ahead of all frame data (config carried in the ISOBMFF sample entry). + // config ahead of all frame data (config carried in the ISOBMFF sample + // entry). if (*state == TU_STATE_LOCAL_INFO || *state == TU_STATE_SEQUENCE_HEADER || *state == TU_STATE_FRAME_UINT_DATA) { if (xlayer_id != GLOBAL_XLAYER_ID && prev_xlayer_id != GLOBAL_XLAYER_ID && diff --git a/av2/decoder/obu_lcr.c b/av2/decoder/obu_lcr.c index b93399d913..201e6c5513 100644 --- a/av2/decoder/obu_lcr.c +++ b/av2/decoder/obu_lcr.c @@ -106,6 +106,8 @@ static int read_lcr_embedded_layer_info(struct LCRXLayerInfo *xlayer_info, if (mlayer_params->lcr_view_type[i] == VIEW_EXPLICIT) { mlayer_params->lcr_view_id[i] = avm_rb_read_literal(rb, 8); } + mlayer_params->LcrViewId[i] = av2_lcr_view_id( + mlayer_params->lcr_view_type[i], mlayer_params->lcr_view_id[i]); if (i > 0) { mlayer_params->lcr_dependent_layer_map[i] = avm_rb_read_literal(rb, i); } diff --git a/avm/avmdx.h b/avm/avmdx.h index 6856c7974a..33e65725f2 100644 --- a/avm/avmdx.h +++ b/avm/avmdx.h @@ -168,15 +168,19 @@ typedef struct avm_screen_content_tools_info { * extended layer. Populated from Global or Local LCR OBU data. */ typedef struct avm_xlayer_layer_info { - int xlayer_id; /**< Extended layer ID (0-30) */ - int layer_type; /**< 0=texture, 1=auxiliary, 2=stereo, 3=dependent */ - int auxiliary_type; /**< 0=alpha, 1=depth, 2=segmentation, 3=gain_map - (-1 if N/A) */ - int view_type; /**< 0=unspecified, 1=center, 2=left, 3=right, - 4=explicit */ - int max_width; /**< lcr_max_pic_width from RepresentationInfo */ - int max_height; /**< lcr_max_pic_height from RepresentationInfo */ - int num_mlayers; /**< Number of embedded (media) layers */ + int xlayer_id; /**< Extended layer ID (0-30) */ + int layer_type; /**< 0=texture, 1=auxiliary, 2=stereo, 3=dependent */ + int auxiliary_type; /**< 0=alpha, 1=depth, 2=segmentation, 3=gain_map + (-1 if N/A) */ + int view_type; /**< 0=unspecified, 1=center, 2=left, 3=right, + 4=explicit */ + int max_width; /**< lcr_max_pic_width from RepresentationInfo */ + int max_height; /**< lcr_max_pic_height from RepresentationInfo */ + int num_mlayers; /**< Number of embedded (media) layers */ + int derived_view_id; /**< LcrViewId of embedded layer 0, derived from + lcr_view_type and lcr_view_id: view_type for + non-explicit views (0-3), 256 + lcr_view_id for + explicit views (256-511) */ } avm_xlayer_layer_info_t; /*!\brief LCR info exposed by AV2D_GET_LCR_INFO. diff --git a/common/tu_assembler.c b/common/tu_assembler.c index 5e6fdd36fd..4c5757fa0f 100644 --- a/common/tu_assembler.c +++ b/common/tu_assembler.c @@ -12,6 +12,7 @@ #include "common/tu_assembler.h" +#include #include #include #include @@ -1268,6 +1269,11 @@ void populate_global_lcr_from_config(const MultiXLayerConfig *mcfg, ml->lcr_auxiliary_type[m] = xl->auxiliary_type; } ml->lcr_view_type[m] = xl->view_type; + const int view_id = xlayer_config_lcr_view_id(xl); + // VIEW_EXPLICIT needs a valid view_id (checked by config validation). + assert(xl->view_type != VIEW_EXPLICIT || view_id >= 0); + ml->lcr_view_id[m] = view_id; + ml->LcrViewId[m] = av2_lcr_view_id(xl->view_type, view_id); ml->TLayerCount[m] = xl->num_temporal_layers; ml->lcr_tlayer_map[m] = (1 << xl->num_temporal_layers) - 1; // Set resolution flag based on scaling mode. @@ -1300,6 +1306,11 @@ void populate_global_lcr_from_config(const MultiXLayerConfig *mcfg, ml->lcr_auxiliary_type[0] = xl->auxiliary_type; } ml->lcr_view_type[0] = xl->view_type; + const int view_id = xlayer_config_lcr_view_id(xl); + // VIEW_EXPLICIT needs a valid view_id (checked by config validation). + assert(xl->view_type != VIEW_EXPLICIT || view_id >= 0); + ml->lcr_view_id[0] = view_id; + ml->LcrViewId[0] = av2_lcr_view_id(xl->view_type, view_id); ml->TLayerCount[0] = xl->num_temporal_layers; ml->lcr_tlayer_map[0] = (1 << xl->num_temporal_layers) - 1; ml->lcr_same_sh_max_resolution_flag[0] = 1; diff --git a/common/xlayer_config.h b/common/xlayer_config.h index a72c6f3319..2f4a9e33ff 100644 --- a/common/xlayer_config.h +++ b/common/xlayer_config.h @@ -84,6 +84,8 @@ typedef struct XLayerEncConfig { int layer_type; // TEXTURE_LAYER, AUX_LAYER, STEREO_LAYER, etc. int auxiliary_type; // LCR_ALPHA_AUX, LCR_DEPTH_AUX, etc. (if AUX_LAYER) int view_type; // VIEW_UNSPECIFIED, VIEW_LEFT, VIEW_RIGHT, etc. + int view_id; // lcr_view_id (0-255), -1 if not set. Required for + // VIEW_EXPLICIT and not signalled otherwise. int num_temporal_layers; int num_embedded_layers; // Color info @@ -127,6 +129,13 @@ typedef struct XLayerEncConfig { } codec_controls[MAX_CODEC_CONTROLS]; } XLayerEncConfig; +// lcr_view_id is signalled only when lcr_view_type is VIEW_EXPLICIT. +// When it is signalled, it must be 0-255. +static inline int xlayer_config_lcr_view_id(const XLayerEncConfig *xl) { + if (xl->view_type != VIEW_EXPLICIT) return -1; + return (xl->view_id >= 0 && xl->view_id <= 255) ? xl->view_id : -1; +} + // Per operating-point configuration within an OPS set typedef struct OperatingPointConfig { int intent; // OPS intent (display, monitoring, etc.) @@ -224,6 +233,7 @@ static inline void xlayer_config_init(MultiXLayerConfig *cfg) { cfg->xlayers[i].num_temporal_layers = 1; cfg->xlayers[i].num_embedded_layers = 1; cfg->xlayers[i].view_type = VIEW_UNSPECIFIED; + cfg->xlayers[i].view_id = -1; for (int j = 0; j < MAX_NUM_MLAYERS; j++) { cfg->xlayers[i].mlayer_sources[j].input_source_idx = -1; cfg->xlayers[i].mlayer_sources[j].atlas_pos_x = -1; diff --git a/common/xlayer_config_parse.c b/common/xlayer_config_parse.c index d304a8910c..88bd167c7f 100644 --- a/common/xlayer_config_parse.c +++ b/common/xlayer_config_parse.c @@ -248,6 +248,8 @@ static int parse_xlayer_entry(const cJSON *entry, XLayerEncConfig *xlcfg) { const char *vt = json_get_string(entry, "view_type", NULL); if (vt) xlcfg->view_type = parse_view_type(vt); + // lcr_view_id is signalled only when lcr_view_type is VIEW_EXPLICIT. + xlcfg->view_id = json_get_int(entry, "view_id", -1); xlcfg->num_temporal_layers = json_get_int(entry, "num_temporal_layers", 1); xlcfg->num_embedded_layers = json_get_int(entry, "num_embedded_layers", 1); @@ -483,6 +485,7 @@ static int parse_xlayer_entry(const cJSON *entry, XLayerEncConfig *xlcfg) { "layer_type", "auxiliary_type", "view_type", + "view_id", "num_temporal_layers", "num_embedded_layers", "color_primaries", @@ -990,6 +993,30 @@ int validate_multi_xlayer_config(const MultiXLayerConfig *cfg) { seen[id] = 1; } + // lcr_view_id is an 8-bit field that is only signalled for VIEW_EXPLICIT, + // where it is required. view_id is -1 when it is not set. + for (int i = 0; i < cfg->num_xlayers; i++) { + const XLayerEncConfig *xl = &cfg->xlayers[i]; + if (xl->view_type == VIEW_EXPLICIT) { + if (xl->view_id < 0) { + fprintf(stderr, + "Error: xlayer %d has view_type \"explicit\" but no view_id\n", + i); + return -1; + } + if (xl->view_id > 255) { + fprintf(stderr, "Error: xlayer %d has invalid view_id %d (0-255)\n", i, + xl->view_id); + return -1; + } + } else if (xl->view_id != -1) { + fprintf(stderr, + "Warning: xlayer %d sets view_id %d but view_type is not " + "\"explicit\"; view_id is not signalled\n", + i, xl->view_id); + } + } + // Validate input source names are unique and non-empty for (int i = 0; i < cfg->num_input_sources; i++) { if (cfg->input_sources[i].name[0] == '\0') { diff --git a/test/lcr_test.cc b/test/lcr_test.cc index 940b2336c3..4f4a2f76ad 100644 --- a/test/lcr_test.cc +++ b/test/lcr_test.cc @@ -504,6 +504,27 @@ TEST_F(LcrTest, GlobalLcrWithEmbeddedLayerInfo) { EXPECT_EQ(dml->lcr_view_id[0], 0); EXPECT_EQ(dml->lcr_layer_type[1], STEREO_LAYER); EXPECT_EQ(dml->lcr_view_id[1], 1); + // LcrViewId = 256 + lcr_view_id for VIEW_EXPLICIT. + EXPECT_EQ(dml->LcrViewId[0], 256); + EXPECT_EQ(dml->LcrViewId[1], 257); +} + +// LcrViewId equals lcr_view_type for the non-explicit view types (lcr_view_id +// is not signalled and must be ignored), and 256 + lcr_view_id otherwise. +TEST(LcrViewIdTest, Derivation) { + EXPECT_EQ(av2_lcr_view_id(VIEW_UNSPECIFIED, 0), 0); + EXPECT_EQ(av2_lcr_view_id(VIEW_CENTER, 0), 1); + EXPECT_EQ(av2_lcr_view_id(VIEW_LEFT, 0), 2); + EXPECT_EQ(av2_lcr_view_id(VIEW_RIGHT, 0), 3); + // A stale lcr_view_id must not affect non-explicit views. + EXPECT_EQ(av2_lcr_view_id(VIEW_LEFT, 7), 2); + EXPECT_EQ(av2_lcr_view_id(VIEW_EXPLICIT, 0), 256); + EXPECT_EQ(av2_lcr_view_id(VIEW_EXPLICIT, 1), 257); + EXPECT_EQ(av2_lcr_view_id(VIEW_EXPLICIT, 255), 511); + // Explicit views never collide with the other view types. + for (int id = 0; id < 256; ++id) { + EXPECT_GE(av2_lcr_view_id(VIEW_EXPLICIT, id), 256); + } } // Sweep all 16 combinations of xlayer_info present flags. @@ -593,6 +614,7 @@ TEST_F(LcrTest, ImplicitViewType) { const EmbeddedLayerInfo *dml = &pbi_->lcr_list[0][1].local_lcr.xlayer_info.mlayer_params; EXPECT_EQ(dml->lcr_view_type[0], VIEW_LEFT); + EXPECT_EQ(dml->LcrViewId[0], VIEW_LEFT); } TEST_F(LcrTest, GlobalLcrMinimalFields) { diff --git a/test/xlayer_config_test.cc b/test/xlayer_config_test.cc index 956936534a..e8e359b042 100644 --- a/test/xlayer_config_test.cc +++ b/test/xlayer_config_test.cc @@ -65,6 +65,7 @@ TEST(XLayerConfig, InitDefaults) { EXPECT_EQ(cfg.xlayers[i].num_temporal_layers, 1); EXPECT_EQ(cfg.xlayers[i].num_embedded_layers, 1); EXPECT_EQ(cfg.xlayers[i].view_type, VIEW_UNSPECIFIED); + EXPECT_EQ(cfg.xlayers[i].view_id, -1); } } @@ -311,6 +312,25 @@ TEST(XLayerConfigParse, MissingInputField) { EXPECT_NE(validate_multi_xlayer_config(&cfg), 0); } +TEST(XLayerConfigParse, ViewId) { + const char *json = R"({ + "xlayers": [ + { "xlayer_id": 0, "input": "a.raw", "view_type": "explicit", + "view_id": 3 }, + { "xlayer_id": 1, "input": "b.raw", "view_type": "left" } + ] + })"; + const char *path = WriteTempJson(json); + ASSERT_NE(path, nullptr); + + MultiXLayerConfig cfg; + ASSERT_EQ(parse_multi_xlayer_config(path, &cfg), 0); + EXPECT_EQ(cfg.xlayers[0].view_type, VIEW_EXPLICIT); + EXPECT_EQ(cfg.xlayers[0].view_id, 3); + EXPECT_EQ(cfg.xlayers[1].view_id, -1); + EXPECT_EQ(validate_multi_xlayer_config(&cfg), 0); +} + // --- Validation Tests --- TEST(XLayerConfigValidate, ValidTwoLayers) { @@ -325,6 +345,59 @@ TEST(XLayerConfigValidate, ValidTwoLayers) { EXPECT_EQ(validate_multi_xlayer_config(&cfg), 0); } +TEST(XLayerConfigValidate, ViewId) { + MultiXLayerConfig cfg; + xlayer_config_init(&cfg); + cfg.num_xlayers = 2; + cfg.xlayers[0].xlayer_id = 0; + snprintf(cfg.xlayers[0].input_filename, PATH_MAX, "a.raw"); + cfg.xlayers[1].xlayer_id = 1; + snprintf(cfg.xlayers[1].input_filename, PATH_MAX, "b.raw"); + + // VIEW_EXPLICIT requires view_id in 0-255. + cfg.xlayers[0].view_type = VIEW_EXPLICIT; + EXPECT_NE(validate_multi_xlayer_config(&cfg), 0); // view_id not set + cfg.xlayers[0].view_id = 256; + EXPECT_NE(validate_multi_xlayer_config(&cfg), 0); + cfg.xlayers[0].view_id = 0; + EXPECT_EQ(validate_multi_xlayer_config(&cfg), 0); + cfg.xlayers[0].view_id = 255; + EXPECT_EQ(validate_multi_xlayer_config(&cfg), 0); + + // Other view types do not need view_id; a value is only warned about. + cfg.xlayers[0].view_type = VIEW_LEFT; + cfg.xlayers[0].view_id = -1; + EXPECT_EQ(validate_multi_xlayer_config(&cfg), 0); + cfg.xlayers[0].view_id = 300; + EXPECT_EQ(validate_multi_xlayer_config(&cfg), 0); +} + +TEST(XLayerConfigViewId, SignalledLcrViewId) { + MultiXLayerConfig cfg; + xlayer_config_init(&cfg); + XLayerEncConfig *xl = &cfg.xlayers[0]; + + // Not signalled (-1) for every view type other than VIEW_EXPLICIT, even if + // view_id is set. + for (int vt = VIEW_UNSPECIFIED; vt < VIEW_EXPLICIT; ++vt) { + xl->view_type = vt; + xl->view_id = -1; + EXPECT_EQ(xlayer_config_lcr_view_id(xl), -1); + xl->view_id = 7; + EXPECT_EQ(xlayer_config_lcr_view_id(xl), -1); + } + // VIEW_EXPLICIT: the configured value if valid, -1 otherwise. + xl->view_type = VIEW_EXPLICIT; + xl->view_id = 0; + EXPECT_EQ(xlayer_config_lcr_view_id(xl), 0); + xl->view_id = 255; + EXPECT_EQ(xlayer_config_lcr_view_id(xl), 255); + xl->view_id = -1; + EXPECT_EQ(xlayer_config_lcr_view_id(xl), -1); + xl->view_id = 256; + EXPECT_EQ(xlayer_config_lcr_view_id(xl), -1); +} + TEST(XLayerConfigValidate, DuplicateXlayerId) { MultiXLayerConfig cfg; xlayer_config_init(&cfg); diff --git a/third_party/cJSON/cJSON.c b/third_party/cJSON/cJSON.c index cd3ff5206e..a47478f33d 100644 --- a/third_party/cJSON/cJSON.c +++ b/third_party/cJSON/cJSON.c @@ -311,8 +311,7 @@ void cJSON_Delete(cJSON *item) { cJSON_Delete(item->child); if (!(item->type & cJSON_IsReference) && item->valuestring) free(item->valuestring); - if (!(item->type & cJSON_StringIsConst) && item->string) - free(item->string); + if (!(item->type & cJSON_StringIsConst) && item->string) free(item->string); free(item); item = next; } diff --git a/third_party/cJSON/cJSON.h b/third_party/cJSON/cJSON.h index 6432be13d1..e1d37be990 100644 --- a/third_party/cJSON/cJSON.h +++ b/third_party/cJSON/cJSON.h @@ -105,7 +105,7 @@ char *cJSON_GetStringValue(const cJSON *item); double cJSON_GetNumberValue(const cJSON *item); /* Macro to iterate over array/object children */ -#define cJSON_ArrayForEach(element, array) \ +#define cJSON_ArrayForEach(element, array) \ for (element = (array != NULL) ? (array)->child : NULL; element != NULL; \ element = element->next)