/*
 * Copyright (c) 2021, Alliance for Open Media. All rights reserved
 *
 * This source code is subject to the terms of the BSD 3-Clause Clear License
 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
 * License was not distributed with this source code in the LICENSE file, you
 * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/.  If the
 * Alliance for Open Media Patent License 1.0 was not distributed with this
 * source code in the PATENTS file, you can obtain it at
 * aomedia.org/license/patent-license/.
 */

#include <assert.h>

#include "config/avm_config.h"
#include "av2/common/banding_metadata.h"
#include "config/avm_scale_rtcd.h"

#include "avm/avm_codec.h"
#include "avm_dsp/bitreader_buffer.h"
#include "avm_mem/avm_mem.h"
#include "avm_ports/mem_ops.h"

#include "av2/common/common.h"
#include "av2/common/obu_util.h"
#include "av2/common/level.h"
#include "av2/common/timing.h"
#include "av2/decoder/decoder.h"
#include "av2/decoder/decodeframe.h"
#include "av2/decoder/obu.h"
#include "av2/common/enums.h"
#include "av2/common/annexA.h"
#include "av2/decoder/annexF.h"

static uint32_t read_temporal_delimiter_obu() { return 0; }

// Returns a boolean that indicates success.
static int read_bitstream_level(AV2_LEVEL *seq_level_idx,
                                struct avm_read_bit_buffer *rb) {
  *seq_level_idx = avm_rb_read_literal(rb, LEVEL_BITS);
  if (!is_valid_seq_level_idx(*seq_level_idx)) return 0;
  return 1;
}

static void av2_read_tlayer_dependency_info(SequenceHeader *const seq,
                                            struct avm_read_bit_buffer *rb) {
  const int max_mlayer_id = seq->max_mlayer_id;
  const int max_tlayer_id = seq->max_tlayer_id;
  const int multi_tlayer_flag = seq->multi_tlayer_dependency_map_present_flag;
  for (int curr_mlayer_id = 0; curr_mlayer_id <= max_mlayer_id;
       curr_mlayer_id++) {
    for (int curr_tlayer_id = 1; curr_tlayer_id <= max_tlayer_id;
         curr_tlayer_id++) {
      for (int ref_tlayer_id = curr_tlayer_id; ref_tlayer_id >= 0;
           ref_tlayer_id--) {
        if (multi_tlayer_flag > 0 || curr_mlayer_id == 0) {
          seq->tlayer_dependency_map[curr_mlayer_id][curr_tlayer_id]
                                    [ref_tlayer_id] = avm_rb_read_bit(rb);
        } else {
          seq->tlayer_dependency_map[curr_mlayer_id][curr_tlayer_id]
                                    [ref_tlayer_id] =
              seq->tlayer_dependency_map[0][curr_tlayer_id][ref_tlayer_id];
        }
      }
    }
  }
}

static void av2_read_mlayer_dependency_info(SequenceHeader *const seq,
                                            struct avm_read_bit_buffer *rb) {
  const int max_mlayer_id = seq->max_mlayer_id;
  for (int curr_mlayer_id = 1; curr_mlayer_id <= max_mlayer_id;
       curr_mlayer_id++) {
    for (int ref_mlayer_id = curr_mlayer_id; ref_mlayer_id >= 0;
         ref_mlayer_id--) {
      seq->mlayer_dependency_map[curr_mlayer_id][ref_mlayer_id] =
          avm_rb_read_bit(rb);
    }
  }
}

// This function validates the conformance window params
static void av2_validate_seq_conformance_window(
    const struct SequenceHeader *seq_params,
    struct avm_internal_error_info *error_info) {
  const struct CropWindow *conf = &seq_params->conf;
  if (!conf->conf_win_enabled_flag) return;

  if (conf->conf_win_left_offset >= seq_params->max_frame_width) {
    avm_internal_error(
        error_info, AVM_CODEC_UNSUP_BITSTREAM,
        "Conformance window left offset %d exceeds max width %d\n",
        conf->conf_win_left_offset, seq_params->max_frame_width);
  }

  if (conf->conf_win_right_offset >= seq_params->max_frame_width) {
    avm_internal_error(
        error_info, AVM_CODEC_UNSUP_BITSTREAM,
        "Conformance window right offset %d exceeds max width %d\n",
        conf->conf_win_right_offset, seq_params->max_frame_width);
  }

  if (conf->conf_win_top_offset >= seq_params->max_frame_height) {
    avm_internal_error(
        error_info, AVM_CODEC_UNSUP_BITSTREAM,
        "Conformance window top offset %d exceeds max height %d\n",
        conf->conf_win_top_offset, seq_params->max_frame_height);
  }

  if (conf->conf_win_bottom_offset >= seq_params->max_frame_height) {
    avm_internal_error(
        error_info, AVM_CODEC_UNSUP_BITSTREAM,
        "Conformance window bottom offset %d exceeds max height %d\n",
        conf->conf_win_bottom_offset, seq_params->max_frame_height);
  }
}

// Returns whether two sequence headers are consistent with each other.
// Note that the 'op_params' field is not compared per Section 7.5 in the spec:
//   Within a particular coded video sequence, the contents of
//   sequence_header_obu must be bit-identical each time the sequence header
//   appears except for the contents of operating_parameters_info.
int are_seq_headers_consistent(const SequenceHeader *seq_params_old,
                               const SequenceHeader *seq_params_new) {
  return !memcmp(seq_params_old, seq_params_new,
                 offsetof(SequenceHeader, op_params));
}

void av2_read_color_info(int *color_description_idc, int *color_primaries,
                         int *transfer_characteristics,
                         int *matrix_coefficients, int *full_range_flag,
                         struct avm_read_bit_buffer *rb) {
  // color_description_idc: indicates the combination of color primaries,
  // transfer characteristics and matrix coefficients as defined in Section
  // 6.10.4 (Operating point set color info semantics) in the spec.
  // The value of color_description_idc shall be in the range of 0 to 127,
  // inclusive. Values larger than 5 are reserved for future use by AOMedia and
  // should be ignored by decoders conforming to this version of this
  // specification.
  *color_description_idc = avm_rb_read_rice_golomb(rb, 2);
  if (*color_description_idc > 127) {
    rb->error_handler(rb->error_handler_data, AVM_CODEC_UNSUP_BITSTREAM,
                      "color_description_idc is not in the range of 0 to 127");
  }
  switch (*color_description_idc) {
    case AVM_COLOR_DESC_IDC_EXPLICIT:  // 0
      *color_primaries = avm_rb_read_literal(rb, 8);
      *transfer_characteristics = avm_rb_read_literal(rb, 8);
      *matrix_coefficients = avm_rb_read_literal(rb, 8);
      break;
    case AVM_COLOR_DESC_IDC_BT709SDR:                  // 1
      *color_primaries = AVM_CICP_CP_BT_709;           // 1
      *transfer_characteristics = AVM_CICP_TC_BT_709;  // 1
      *matrix_coefficients = AVM_CICP_MC_BT_709;       // 1
      break;
    case AVM_COLOR_DESC_IDC_BT2100PQ:                      // 2
      *color_primaries = AVM_CICP_CP_BT_2020;              // 9
      *transfer_characteristics = AVM_CICP_TC_SMPTE_2084;  // 16
      *matrix_coefficients = AVM_CICP_MC_BT_2020_NCL;      // 9
      break;
    case AVM_COLOR_DESC_IDC_BT2100HLG:                 // 3
      *color_primaries = AVM_CICP_CP_BT_2020;          // 9
      *transfer_characteristics = AVM_CICP_TC_HLG;     // 18
      *matrix_coefficients = AVM_CICP_MC_BT_2020_NCL;  // 9
      break;
    case AVM_COLOR_DESC_IDC_SRGB:                    // 4
      *color_primaries = AVM_CICP_CP_BT_709;         // 1
      *transfer_characteristics = AVM_CICP_TC_SRGB;  // 13
      *matrix_coefficients = AVM_CICP_MC_IDENTITY;   // 0
      break;
    case AVM_COLOR_DESC_IDC_SYCC:                     // 5
      *color_primaries = AVM_CICP_CP_BT_709;          // 1
      *transfer_characteristics = AVM_CICP_TC_SRGB;   // 13
      *matrix_coefficients = AVM_CICP_MC_BT_470_B_G;  // 5
      break;
    default:
      // Values larger than 5 are reserved for future use by AOMedia and should
      // be ignored by decoders.
      *color_primaries = AVM_CICP_CP_UNSPECIFIED;
      *transfer_characteristics = AVM_CICP_TC_UNSPECIFIED;
      *matrix_coefficients = AVM_CICP_MC_UNSPECIFIED;
      break;
  }
  *full_range_flag = avm_rb_read_bit(rb);
}

// Helper function to store xlayer context
// Helper function to map xlayer_id to stream_id array index
int av2_get_stream_index(const AV2_COMMON *cm, int xlayer_id) {
  // GLOBAL_XLAYER_ID doesn't use stream_info
  if (xlayer_id == GLOBAL_XLAYER_ID) {
    return -1;
  }

  // Find which index in stream_ids matches this xlayer_id
  for (int i = 0; i < cm->num_streams; i++) {
    if (cm->stream_ids[i] == xlayer_id) {
      return i;
    }
  }

  // Should never happen with valid bitstream
  return -1;
}

void av2_store_xlayer_context(AV2Decoder *pbi, AV2_COMMON *cm, int xlayer_id) {
  int stream_idx = av2_get_stream_index(cm, xlayer_id);
  if (stream_idx < 0) return;  // Invalid or GLOBAL_XLAYER_ID

  for (int i = 0; i < REF_FRAMES; i++) {
    pbi->stream_info[stream_idx].ref_frame_map_buf[i] = cm->ref_frame_map[i];
    pbi->stream_info[stream_idx].valid_for_referencing_buf[i] =
        pbi->valid_for_referencing[i];
  }
  for (int i = 0; i < INTER_REFS_PER_FRAME; i++) {
    pbi->stream_info[stream_idx].remapped_ref_idx_buf[i] =
        cm->remapped_ref_idx[i];
  }
  for (int i = 0; i < MAX_MFH_NUM; i++) {
    pbi->stream_info[stream_idx].mfh_params_buf[i] = cm->mfh_params[i];
  }

  // Global OBUs (xlayer_id=31) excluded from per-layer save/restore
  pbi->stream_info[stream_idx].lcr_counter_buf = pbi->lcr_counter;
  pbi->stream_info[stream_idx].active_lcr_buf = pbi->active_lcr;
  pbi->stream_info[stream_idx].active_atlas_segment_info_buf =
      pbi->active_atlas_segment_info;
  pbi->stream_info[stream_idx].ops_counter_buf = pbi->ops_counter;

  for (int i = 0; i < NUM_CUSTOM_QMS; i++) {
    pbi->stream_info[stream_idx].qm_list_buf[i] = pbi->qm_list[i];
    pbi->stream_info[stream_idx].qm_protected_buf[i] = pbi->qm_protected[i];
  }
  pbi->stream_info[stream_idx].olk_encountered_buf = pbi->olk_encountered;
  pbi->stream_info[stream_idx].random_access_point_index_buf =
      pbi->random_access_point_index;
  pbi->stream_info[stream_idx].random_access_point_count_buf =
      pbi->random_access_point_count;
  for (int i = 0; i < MAX_FGM_NUM; i++) {
    pbi->stream_info[stream_idx].fgm_list_buf[i] = pbi->fgm_list[i];
  }
  pbi->stream_info[stream_idx].prev_frame_buf = cm->prev_frame;
  pbi->stream_info[stream_idx].last_frame_seg_map_buf = cm->last_frame_seg_map;
  for (int i = 0; i < MAX_NUM_MLAYERS; i++) {
    pbi->stream_info[stream_idx].ci_params_per_layer_buf[i] =
        cm->ci_params_per_layer[i];
  }
  for (int i = 0; i < MAX_NUM_MLAYERS; i++) {
    pbi->stream_info[stream_idx].olk_refresh_frame_flags_buf[i] =
        cm->olk_refresh_frame_flags[i];
    pbi->stream_info[stream_idx].olk_co_vcl_refresh_frame_flags_buf[i] =
        cm->olk_co_vcl_refresh_frame_flags[i];
  }
  pbi->stream_info[stream_idx].seq_params_buf = cm->seq_params;
  for (int i = 0; i < MAX_MFH_NUM; i++) {
    pbi->stream_info[stream_idx].mfh_valid_buf[i] = cm->mfh_valid[i];
  }

  pbi->stream_info[stream_idx].decoding_first_frame = pbi->decoding_first_frame;
  pbi->stream_info[stream_idx].last_olk_tu_display_order_hint =
      pbi->last_olk_tu_display_order_hint;
  pbi->stream_info[stream_idx].seen_frame_header_buf = pbi->seen_frame_header;
  pbi->stream_info[stream_idx].next_start_tile_buf = pbi->next_start_tile;
  pbi->stream_info[stream_idx].seen_vcl_obu_in_this_tu_buf =
      pbi->seen_vcl_obu_in_this_tu;
}

// Helper function to restore xlayer context
void av2_restore_xlayer_context(AV2Decoder *pbi, AV2_COMMON *cm,
                                int xlayer_id) {
  int stream_idx = av2_get_stream_index(cm, xlayer_id);
  if (stream_idx < 0) return;  // Invalid or GLOBAL_XLAYER_ID

  // Check if stream_info is valid
  if (pbi->stream_info == NULL) {
    return;
  }

  for (int i = 0; i < REF_FRAMES; i++) {
    cm->ref_frame_map[i] = pbi->stream_info[stream_idx].ref_frame_map_buf[i];
    pbi->valid_for_referencing[i] =
        pbi->stream_info[stream_idx].valid_for_referencing_buf[i];
  }
  for (int i = 0; i < INTER_REFS_PER_FRAME; i++) {
    cm->remapped_ref_idx[i] =
        pbi->stream_info[stream_idx].remapped_ref_idx_buf[i];
  }
  for (int i = 0; i < MAX_MFH_NUM; i++) {
    cm->mfh_params[i] = pbi->stream_info[stream_idx].mfh_params_buf[i];
  }

  // Global OBUs (xlayer_id=31) excluded from per-layer save/restore
  pbi->lcr_counter = pbi->stream_info[stream_idx].lcr_counter_buf;
  pbi->active_lcr = pbi->stream_info[stream_idx].active_lcr_buf;
  pbi->active_atlas_segment_info =
      pbi->stream_info[stream_idx].active_atlas_segment_info_buf;
  pbi->ops_counter = pbi->stream_info[stream_idx].ops_counter_buf;
  for (int i = 0; i < NUM_CUSTOM_QMS; i++) {
    pbi->qm_list[i] = pbi->stream_info[stream_idx].qm_list_buf[i];
    pbi->qm_protected[i] = pbi->stream_info[stream_idx].qm_protected_buf[i];
  }
  pbi->olk_encountered = pbi->stream_info[stream_idx].olk_encountered_buf;
  pbi->random_access_point_index =
      pbi->stream_info[stream_idx].random_access_point_index_buf;
  pbi->random_access_point_count =
      pbi->stream_info[stream_idx].random_access_point_count_buf;
  for (int i = 0; i < MAX_FGM_NUM; i++) {
    pbi->fgm_list[i] = pbi->stream_info[stream_idx].fgm_list_buf[i];
  }
  cm->prev_frame = pbi->stream_info[stream_idx].prev_frame_buf;
  cm->last_frame_seg_map = pbi->stream_info[stream_idx].last_frame_seg_map_buf;
  for (int i = 0; i < MAX_NUM_MLAYERS; i++) {
    cm->ci_params_per_layer[i] =
        pbi->stream_info[stream_idx].ci_params_per_layer_buf[i];
  }
  for (int i = 0; i < MAX_NUM_MLAYERS; i++) {
    cm->olk_refresh_frame_flags[i] =
        pbi->stream_info[stream_idx].olk_refresh_frame_flags_buf[i];
    cm->olk_co_vcl_refresh_frame_flags[i] =
        pbi->stream_info[stream_idx].olk_co_vcl_refresh_frame_flags_buf[i];
  }
  cm->seq_params = pbi->stream_info[stream_idx].seq_params_buf;
  for (int i = 0; i < MAX_MFH_NUM; i++) {
    cm->mfh_valid[i] = pbi->stream_info[stream_idx].mfh_valid_buf[i];
  }

  pbi->decoding_first_frame = pbi->stream_info[stream_idx].decoding_first_frame;
  pbi->last_olk_tu_display_order_hint =
      pbi->stream_info[stream_idx].last_olk_tu_display_order_hint;
  pbi->seen_frame_header = pbi->stream_info[stream_idx].seen_frame_header_buf;
  pbi->next_start_tile = pbi->stream_info[stream_idx].next_start_tile_buf;
  pbi->seen_vcl_obu_in_this_tu =
      pbi->stream_info[stream_idx].seen_vcl_obu_in_this_tu_buf;
}

static void init_stream_info(StreamInfo *stream_info) {
  stream_info->olk_encountered_buf = 0;
  stream_info->random_access_point_index_buf = -1;
  stream_info->random_access_point_count_buf = 0;
  for (int i = 0; i < INTER_REFS_PER_FRAME; ++i) {
    stream_info->remapped_ref_idx_buf[i] = INVALID_IDX;
  }
  for (int i = 0; i < REF_FRAMES; i++) {
    stream_info->ref_frame_map_buf[i] = NULL;
  }
  stream_info->mfh_valid_buf[0] = true;
  for (int i = 1; i < MAX_MFH_NUM; i++) {
    stream_info->mfh_valid_buf[i] = false;
  }
  for (int i = 0; i < MAX_NUM_MLAYERS; i++) {
    av2_initialize_ci_params(&stream_info->ci_params_per_layer_buf[i]);
  }
  stream_info->decoding_first_frame = 1;
  stream_info->last_olk_tu_display_order_hint = -1;
}

/*!
 * \brief Save current MSDO configuration
 */
static void save_msdo_config(const AV2Decoder *pbi, MsdoConfig *config) {
  const AV2_COMMON *const cm = &pbi->common;
  config->multistream_profile_idc =
      pbi->common.msdo_params.multistream_profile_idc;
  config->multistream_tier_idx = pbi->common.msdo_params.multistream_tier_idx;
  config->multistream_level_idx = pbi->common.msdo_params.multistream_level_idx;
  config->num_streams = cm->num_streams;
  memcpy(config->stream_ids, cm->stream_ids, cm->num_streams * sizeof(int));
}

/*!
 * \brief Compare two MSDO configurations
 * \return true if configurations are identical, false otherwise
 */
static bool msdo_config_equal(const MsdoConfig *a, const MsdoConfig *b) {
  if (a->multistream_profile_idc != b->multistream_profile_idc) return false;
  if (a->multistream_tier_idx != b->multistream_tier_idx) return false;
  if (a->multistream_level_idx != b->multistream_level_idx) return false;
  if (a->num_streams != b->num_streams) return false;

  return true;
}

avm_codec_err_t flush_all_xlayer_frames(struct AV2Decoder *pbi, AV2_COMMON *cm,
                                        bool release_dpb) {
  BufferPool *const pool = cm->buffer_pool;
  int saved_xlayer_id = cm->xlayer_id;
  avm_codec_err_t err = AVM_CODEC_OK;

  for (int xlayer = 0; xlayer < AVM_MAX_NUM_STREAMS; xlayer++) {
    if (xlayer == GLOBAL_XLAYER_ID) continue;  // Global context is not a stream
    if (pbi->xlayer_id_map[xlayer] > 0) {
      if (xlayer != cm->xlayer_id) {
        av2_store_xlayer_context(pbi, cm, cm->xlayer_id);
        cm->xlayer_id = xlayer;
        av2_restore_xlayer_context(pbi, cm, xlayer);
      }
      size_t num_before = pbi->num_output_frames;
      err = flush_remaining_frames(pbi, INT_MAX);
      if (err != AVM_CODEC_OK) break;
      for (size_t j = num_before; j < pbi->num_output_frames; j++) {
        decrease_ref_count(pbi->output_frames[j], pool);
      }
      if (release_dpb) {
        for (int r = 0; r < REF_FRAMES; r++) {
          decrease_ref_count(cm->ref_frame_map[r], pool);
          cm->ref_frame_map[r] = NULL;
        }
      }
    }
  }

  if (cm->xlayer_id != saved_xlayer_id) {
    av2_store_xlayer_context(pbi, cm, cm->xlayer_id);
    cm->xlayer_id = saved_xlayer_id;
    av2_restore_xlayer_context(pbi, cm, saved_xlayer_id);
  }
  return err;
}

static uint32_t read_multi_stream_decoder_operation_obu(
    AV2Decoder *pbi, struct avm_read_bit_buffer *rb) {
  AV2_COMMON *const cm = &pbi->common;
  const uint32_t saved_bit_offset = rb->bit_offset;

  // Verify rb has been configured to report errors.
  assert(rb->error_handler);

  // Save previous configuration if it exists
  MsdoConfig prev_config;
  bool has_previous = false;

  if (pbi->stream_info != NULL) {
    save_msdo_config(pbi, &prev_config);
    has_previous = true;
  }

  const int num_streams =
      avm_rb_read_literal(rb, 3) + 2;  // read number of streams
  if (num_streams > AVM_MAX_NUM_STREAMS) {
    avm_internal_error(
        &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
        "The number of streams cannot exceed the max value (4).");
  }
  cm->num_streams = num_streams;

  pbi->common.msdo_params.multistream_profile_idc =
      avm_rb_read_literal(rb, PROFILE_BITS);  // read profile of multistream

  pbi->common.msdo_params.multistream_level_idx =
      avm_rb_read_literal(rb, LEVEL_BITS);  // read level of multistream

  pbi->common.msdo_params.multistream_tier_idx =
      avm_rb_read_bit(rb);  // read tier of multistream

  const int multistream_even_allocation_flag =
      avm_rb_read_bit(rb);  // read multistream_even_allocation_flag

  if (!multistream_even_allocation_flag) {
    const int multistream_large_picture_idc =
        avm_rb_read_literal(rb, 3);  // read multistream_large_picture_idc
    (void)multistream_large_picture_idc;
  }

  for (int i = 0; i < num_streams; i++) {
    cm->stream_ids[i] = avm_rb_read_literal(rb, XLAYER_BITS);  // read stream ID
    const int substream_profile_idc =
        avm_rb_read_literal(rb, PROFILE_BITS);  // read profile of multistream
    (void)substream_profile_idc;

    const int substream_level_idx =
        avm_rb_read_literal(rb, LEVEL_BITS);  // read level of multistream
    (void)substream_level_idx;

    const int substream_tier_idx =
        avm_rb_read_bit(rb);  // read tier of multistream
    (void)substream_tier_idx;
  }

  cm->msdo_params.msdo_doh_constraint_flag = avm_rb_read_bit(rb);

  // Check if configuration changed
  MsdoConfig new_config;
  save_msdo_config(pbi, &new_config);
  bool config_changed =
      !has_previous || !msdo_config_equal(&prev_config, &new_config);

  // Flush remaining frames from all active streams before switching config
  if (pbi->stream_info != NULL && config_changed) {
    flush_all_xlayer_frames(pbi, cm, true);
    avm_free(pbi->stream_info);
    pbi->stream_info = NULL;
    pbi->glcr_stream_info_num_allocated = 0;

    // Reset xlayer_id_map so only the new segment's layers are active
    for (int i = 0; i < AVM_MAX_NUM_STREAMS; i++) {
      pbi->xlayer_id_map[i] = 0;
    }
  }

  // Only allocate if stream_info is NULL (first time OR after freeing due to
  // config change)
  if (pbi->stream_info == NULL) {
    pbi->stream_info =
        (StreamInfo *)avm_malloc(num_streams * sizeof(StreamInfo));
    if (pbi->stream_info == NULL) {
      avm_internal_error(&cm->error, AVM_CODEC_MEM_ERROR,
                         "Memory allocation failed for pbi->stream_info\n");
    }
    memset(pbi->stream_info, 0, num_streams * sizeof(StreamInfo));
    for (int i = 0; i < num_streams; i++) {
      init_stream_info(&pbi->stream_info[i]);
    }
  }

  pbi->msdo_is_present_in_tu = 1;

  if (av2_check_trailing_bits(pbi, rb) != 0) {
    return 0;
  }

  return ((rb->bit_offset - saved_bit_offset + 7) >> 3);
}

// On success, returns the number of bytes read from 'rb'.
// On failure, sets pbi->common.error.error_code and returns 0.
static uint32_t read_sequence_header_obu(AV2Decoder *pbi, int xlayer_id,
                                         struct avm_read_bit_buffer *rb,
                                         uint16_t *acc_sh_id_bitmap) {
  AV2_COMMON *const cm = &pbi->common;
  const uint32_t saved_bit_offset = rb->bit_offset;

  // Verify rb has been configured to report errors.
  assert(rb->error_handler);

  // Use an element in the pbi->seq_list array to store the information as we
  // decode. At the end, if no errors have occurred, cm->seq_params is updated.
  uint32_t seq_header_id = avm_rb_read_uvlc(rb);
  if (seq_header_id >= MAX_SEQ_NUM) {
    cm->error.error_code = AVM_CODEC_UNSUP_BITSTREAM;
    return 0;
  }

  struct SequenceHeader *seq_params = &pbi->seq_list[xlayer_id][seq_header_id];
  seq_params->seq_header_id = seq_header_id;
  seq_params->sh_from_leading = 0;
  acc_sh_id_bitmap[xlayer_id] |= (1 << seq_header_id);

  seq_params->seq_profile_idc = av2_read_profile(rb);
  if (seq_params->seq_profile_idc >= MAX_PROFILES) {
    cm->error.error_code = AVM_CODEC_UNSUP_BITSTREAM;
    return 0;
  }

  seq_params->single_picture_header_flag = avm_rb_read_bit(rb);
  if (!read_bitstream_level(&seq_params->seq_max_level_idx, rb)) {
    cm->error.error_code = AVM_CODEC_UNSUP_BITSTREAM;
    return 0;
  }
  if (seq_params->seq_max_level_idx >= SEQ_LEVEL_4_0 &&
      !seq_params->single_picture_header_flag)
    seq_params->seq_tier = avm_rb_read_bit(rb);
  else
    seq_params->seq_tier = 0;
  av2_read_chroma_format_bitdepth(rb, seq_params, &cm->error);
  if (seq_params->single_picture_header_flag) {
    seq_params->seq_lcr_id = LCR_ID_UNSPECIFIED;
    seq_params->still_picture = 1;
    seq_params->max_tlayer_id = 0;
    seq_params->max_mlayer_id = 0;
    seq_params->seq_max_mlayer_cnt = 1;
    seq_params->monotonic_output_order_flag = 1;
  } else {
    int seq_lcr_id = avm_rb_read_literal(rb, 3);
    if (seq_lcr_id > MAX_NUM_SEQ_LCR_ID) {
      avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                         "Unsupported LCR id in the Sequence Header.\n");
    }
    seq_params->seq_lcr_id = seq_lcr_id;
    seq_params->still_picture = avm_rb_read_bit(rb);
    seq_params->max_tlayer_id = avm_rb_read_literal(rb, TLAYER_BITS);
    seq_params->max_mlayer_id = avm_rb_read_literal(rb, MLAYER_BITS);
    if (seq_params->max_mlayer_id > 0) {
      int n = avm_ceil_log2(seq_params->max_mlayer_id + 1);
      int seq_max_mlayer_cnt_minus_1 = avm_rb_read_literal(rb, n);
      if (seq_max_mlayer_cnt_minus_1 > seq_params->max_mlayer_id) {
        avm_internal_error(
            &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
            "seq_max_mlayer_cnt_minus_1 %d is greater than max_mlayer_id %d",
            seq_max_mlayer_cnt_minus_1, seq_params->max_mlayer_id);
      }
      seq_params->seq_max_mlayer_cnt = seq_max_mlayer_cnt_minus_1 + 1;
    } else {
      seq_params->seq_max_mlayer_cnt = 1;
    }
    seq_params->monotonic_output_order_flag = avm_rb_read_bit(rb);
  }

  const int num_bits_width = avm_rb_read_literal(rb, 4) + 1;
  const int num_bits_height = avm_rb_read_literal(rb, 4) + 1;
  const int max_frame_width = avm_rb_read_literal(rb, num_bits_width) + 1;
  const int max_frame_height = avm_rb_read_literal(rb, num_bits_height) + 1;

  seq_params->num_bits_width = num_bits_width;
  seq_params->num_bits_height = num_bits_height;
  seq_params->max_frame_width = max_frame_width;
  seq_params->max_frame_height = max_frame_height;

  av2_read_conformance_window(rb, seq_params);
  av2_validate_seq_conformance_window(seq_params, &cm->error);

  if (seq_params->single_picture_header_flag) {
    seq_params->decoder_model_info_present_flag = 0;
    seq_params->display_model_info_present_flag = 0;
  } else {
    seq_params->seq_max_display_model_info_present_flag = avm_rb_read_bit(rb);
    seq_params->seq_max_initial_display_delay_minus_1 =
        BUFFER_POOL_MAX_SIZE - 1;
    if (seq_params->seq_max_display_model_info_present_flag)
      seq_params->seq_max_initial_display_delay_minus_1 =
          avm_rb_read_literal(rb, 4);
    seq_params->decoder_model_info_present_flag = avm_rb_read_bit(rb);
    if (seq_params->decoder_model_info_present_flag) {
      seq_params->decoder_model_info.num_units_in_decoding_tick =
          avm_rb_read_unsigned_literal(rb, 32);
      seq_params->seq_max_decoder_model_present_flag = avm_rb_read_bit(rb);
      if (seq_params->seq_max_decoder_model_present_flag) {
        seq_params->seq_max_decoder_buffer_delay = avm_rb_read_uvlc(rb);
        seq_params->seq_max_encoder_buffer_delay = avm_rb_read_uvlc(rb);
        seq_params->seq_max_low_delay_mode_flag = avm_rb_read_bit(rb);
      } else {
        seq_params->seq_max_decoder_buffer_delay = 70000;
        seq_params->seq_max_encoder_buffer_delay = 20000;
        seq_params->seq_max_low_delay_mode_flag = 0;
      }
    } else {
      seq_params->decoder_model_info.num_units_in_decoding_tick = 1;
      seq_params->seq_max_decoder_buffer_delay = 70000;
      seq_params->seq_max_encoder_buffer_delay = 20000;
      seq_params->seq_max_low_delay_mode_flag = 0;
    }
    // Configurable profile does not define bitrate and buffer size constraints
    if (seq_params->seq_profile_idc != CONFIGURABLE) {
      int64_t seq_bitrate = av2_max_level_bitrate(
          seq_params->seq_profile_idc, seq_params->seq_max_level_idx,
          seq_params->seq_tier, seq_params->subsampling_x,
          seq_params->subsampling_y, seq_params->monochrome);
      if (seq_bitrate == 0)
        avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                           "AV2 does not support this combination of "
                           "profile, level, and tier.");
      // Buffer size in bits/s is bitrate in bits/s * 1 s
      int64_t buffer_size = seq_bitrate;
      if (buffer_size == 0)
        avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                           "AV2 does not support this combination of "
                           "profile, level, and tier.");
    }
  }

  // setup default embedded layer dependency
  setup_default_embedded_layer_dependency_structure(seq_params);
  // setup default temporal layer dependency
  setup_default_temporal_layer_dependency_structure(seq_params);

  // mlayer dependency description
  seq_params->mlayer_dependency_present_flag = 0;
  if (seq_params->max_mlayer_id > 0) {
    seq_params->mlayer_dependency_present_flag = avm_rb_read_bit(rb);
    if (seq_params->mlayer_dependency_present_flag) {
      av2_read_mlayer_dependency_info(seq_params, rb);
    }
  }

  // tlayer dependency description
  seq_params->tlayer_dependency_present_flag = 0;
  seq_params->multi_tlayer_dependency_map_present_flag = 0;
  if (seq_params->max_tlayer_id > 0) {
    seq_params->tlayer_dependency_present_flag = avm_rb_read_bit(rb);
    if (seq_params->tlayer_dependency_present_flag) {
      if (seq_params->max_mlayer_id > 0) {
        seq_params->multi_tlayer_dependency_map_present_flag =
            avm_rb_read_bit(rb);
      }
      av2_read_tlayer_dependency_info(seq_params, rb);
    }
  }

  if (!av2_check_profile_interop_conformance(seq_params, &cm->error, 1)) {
    avm_internal_error(
        &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
        "Unsupported Bitdepth, Chroma format or number of embedded layers");
  }

  av2_read_sequence_header(rb, seq_params);

  seq_params->film_grain_params_present = avm_rb_read_bit(rb);

  size_t bits_before_ext = rb->bit_offset - saved_bit_offset;
  seq_params->seq_extension_present_flag = avm_rb_read_bit(rb);
  if (seq_params->seq_extension_present_flag) {
    // Extension data bits = total - bits_read_before_extension -1 (ext flag) -
    // trailing bits
    int extension_bits = read_obu_extension_bits(
        rb->bit_buffer, rb->bit_buffer_end - rb->bit_buffer, bits_before_ext,
        &cm->error);
    if (extension_bits > 0) {
      // skip over the extension bits
      rb->bit_offset += extension_bits;
    } else {
      // No extension data is present
    }
  }

  if (av2_check_trailing_bits(pbi, rb) != 0) {
    // cm->error.error_code is already set.
    return 0;
  }
  return ((rb->bit_offset - saved_bit_offset + 7) >> 3);
}

static uint32_t read_multi_frame_header_obu(AV2Decoder *pbi,
                                            uint32_t *acc_mfh_id_bitmap,
                                            struct avm_read_bit_buffer *rb) {
  AV2_COMMON *const cm = &pbi->common;
  const uint32_t saved_bit_offset = rb->bit_offset;

  const uint32_t cur_mfh_id = av2_read_multi_frame_header(cm, rb);
  assert(cur_mfh_id < MAX_MFH_NUM);
  if (acc_mfh_id_bitmap) {
    *acc_mfh_id_bitmap |= (1 << cur_mfh_id);
  }

  size_t bits_before_ext = rb->bit_offset - saved_bit_offset;
  cm->mfh_params[cur_mfh_id].mfh_extension_present_flag = avm_rb_read_bit(rb);
  if (cm->mfh_params[cur_mfh_id].mfh_extension_present_flag) {
    // Extension data bits = total - bits_read_before_extension -1 (ext flag) -
    // trailing bits
    int extension_bits = read_obu_extension_bits(
        rb->bit_buffer, rb->bit_buffer_end - rb->bit_buffer, bits_before_ext,
        &cm->error);
    if (extension_bits > 0) {
      // skip over the extension bits
      rb->bit_offset += extension_bits;
    } else {
      // No extension data present
    }
  }

  if (av2_check_trailing_bits(pbi, rb) != 0) {
    // cm->error.error_code is already set.
    return 0;
  }

  return ((rb->bit_offset - saved_bit_offset + 7) >> 3);
}

static uint32_t read_tilegroup_obu(AV2Decoder *pbi,
                                   struct avm_read_bit_buffer *rb,
                                   const uint8_t *data, const uint8_t *data_end,
                                   const uint8_t **p_data_end,
                                   OBU_TYPE obu_type, int obu_xlayer_id,
                                   int *is_first_tg, int *is_last_tg) {
  AV2_COMMON *const cm = &pbi->common;
  int start_tile, end_tile;
  int32_t header_size, tg_payload_size;

  assert(rb->bit_offset == 0);
  assert(rb->bit_buffer == data);
  *is_first_tg = 1;  // it is updated by av2_read_tilegroup_header()
  header_size = av2_read_tilegroup_header(pbi, rb, data, p_data_end,
                                          is_first_tg, &start_tile, &end_tile,
                                          obu_type, obu_xlayer_id);

  bool skip_payload = false;
  skip_payload |= (obu_type == OBU_LEADING_SEF);
  skip_payload |= (obu_type == OBU_REGULAR_SEF);
  skip_payload |= (obu_type == OBU_LEADING_TIP);
  skip_payload |= (obu_type == OBU_REGULAR_TIP);
  skip_payload |= cm->bru.frame_inactive_flag;
  skip_payload |= cm->bridge_frame_info.is_bridge_frame;

  if (skip_payload) {
    *is_last_tg = 1;
    tg_payload_size = 0;
    if (av2_check_trailing_bits(pbi, rb) != 0) {
      // cm->error.error_code is already set.
      return 0;
    }
    header_size = (int32_t)avm_rb_bytes_read(rb);
  } else {
    if (av2_check_byte_alignment(cm, rb)) return 0;
    data += header_size;

    av2_decode_tg_tiles_and_wrapup(pbi, data, data_end, p_data_end, start_tile,
                                   end_tile, *is_first_tg);

    tg_payload_size = (uint32_t)(*p_data_end - data);
    *is_last_tg = end_tile == cm->tiles.rows * cm->tiles.cols - 1;
  }
  return header_size + tg_payload_size;
}

// 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;
}

// Allocates metadata that was read and adds it to the decoders metadata array.
static void alloc_read_metadata(AV2Decoder *const pbi,
                                OBU_METADATA_TYPE metadata_type,
                                const uint8_t *data, size_t sz,
                                avm_metadata_insert_flags_t insert_flag) {
  AV2_COMMON *const cm = &pbi->common;
  if (!pbi->metadata) {
    pbi->metadata = avm_img_metadata_array_alloc(0);
    if (!pbi->metadata) {
      avm_internal_error(&cm->error, AVM_CODEC_MEM_ERROR,
                         "Failed to allocate metadata array");
    }
  }
  avm_metadata_t *metadata =
      avm_img_metadata_alloc(metadata_type, data, sz, insert_flag);
  if (!metadata) {
    avm_internal_error(&cm->error, AVM_CODEC_MEM_ERROR,
                       "Error allocating metadata");
  }
  avm_metadata_t **metadata_array =
      (avm_metadata_t **)realloc(pbi->metadata->metadata_array,
                                 (pbi->metadata->sz + 1) * sizeof(metadata));
  if (!metadata_array) {
    avm_img_metadata_free(metadata);
    avm_internal_error(&cm->error, AVM_CODEC_MEM_ERROR,
                       "Error growing metadata array");
  }
  pbi->metadata->metadata_array = metadata_array;
  pbi->metadata->metadata_array[pbi->metadata->sz] = metadata;
  pbi->metadata->sz++;
}

// On failure, calls avm_internal_error() and does not return.
static void read_metadata_itut_t35(AV2Decoder *const pbi, const uint8_t *data,
                                   size_t sz) {
  AV2_COMMON *const cm = &pbi->common;
  if (sz == 0) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "itu_t_t35_country_code is missing");
  }
  int country_code_size = 1;
  if (*data == 0xFF) {
    if (sz == 1) {
      avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                         "itu_t_t35_country_code_extension_byte is missing");
    }
    ++country_code_size;
  }
  const int end_index = (int)sz;
  if (end_index < country_code_size) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "No trailing bits found in ITU-T T.35 metadata OBU");
  }
  alloc_read_metadata(pbi, OBU_METADATA_TYPE_ITUT_T35, data, end_index,
                      AVM_MIF_ANY_FRAME);
}

// On failure, calls avm_internal_error() and does not return.
static void read_metadata_itut_t35_short(AV2Decoder *const pbi,
                                         const uint8_t *data, size_t sz) {
  AV2_COMMON *const cm = &pbi->common;
  if (sz == 0) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "itu_t_t35_country_code is missing");
  }
  int country_code_size = 1;
  if (*data == 0xFF) {
    if (sz == 1) {
      avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                         "itu_t_t35_country_code_extension_byte is missing");
    }
    ++country_code_size;
  }
  int end_index = get_last_nonzero_byte_index(data, sz);
  if (end_index < country_code_size) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "No trailing bits found in ITU-T T.35 metadata OBU");
  }
  // itu_t_t35_payload_bytes is byte aligned. Section 6.7.2 of the spec says:
  //   itu_t_t35_payload_bytes shall be bytes containing data registered as
  //   specified in Recommendation ITU-T T.35.
  // Therefore the first trailing byte should be 0x80.
  if (data[end_index] != 0x80) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "The last nonzero byte of the ITU-T T.35 metadata OBU "
                       "is 0x%02x, should be 0x80.",
                       data[end_index]);
  }

  alloc_read_metadata(pbi, OBU_METADATA_TYPE_ITUT_T35, data, end_index,
                      AVM_MIF_ANY_FRAME);
}
// On success, returns the number of bytes read from 'data'. On failure, calls
// avm_internal_error() and does not return.
static size_t read_metadata_hdr_cll(AV2Decoder *const pbi, const uint8_t *data,
                                    size_t sz) {
  const size_t kHdrCllPayloadSize = 4;
  AV2_COMMON *const cm = &pbi->common;
  if (sz < kHdrCllPayloadSize) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "Incorrect HDR CLL metadata payload size");
  }
  alloc_read_metadata(pbi, OBU_METADATA_TYPE_HDR_CLL, data, kHdrCllPayloadSize,
                      AVM_MIF_ANY_FRAME);
  return kHdrCllPayloadSize;
}

// On success, returns the number of bytes read from 'data'. On failure, calls
// avm_internal_error() and does not return.
static size_t read_metadata_hdr_mdcv(AV2Decoder *const pbi, const uint8_t *data,
                                     size_t sz) {
  const size_t kMdcvPayloadSize = 24;
  AV2_COMMON *const cm = &pbi->common;
  if (sz < kMdcvPayloadSize) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "Incorrect HDR MDCV metadata payload size");
  }
  alloc_read_metadata(pbi, OBU_METADATA_TYPE_HDR_MDCV, data, kMdcvPayloadSize,
                      AVM_MIF_ANY_FRAME);
  return kMdcvPayloadSize;
}

// On success, returns the number of bytes read from 'data'. On failure, calls
// avm_internal_error() and does not return.
static size_t read_metadata_banding_hints(AV2Decoder *const pbi,
                                          const uint8_t *data, size_t sz) {
  AV2_COMMON *const cm = &pbi->common;

  // Validate minimum payload size (at least 3 bits for basic flags)
  if (sz == 0) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "Empty banding hints metadata payload");
  }

  // Decode the banding hints metadata payload
  if (avm_decode_banding_hints_metadata(data, sz, &pbi->band_metadata) == 0) {
    // Successfully decoded
    pbi->band_metadata_present = 1;
  } else {
    pbi->band_metadata_present = 0;
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "Failed to decode banding hints metadata");
  }

  alloc_read_metadata(pbi, OBU_METADATA_TYPE_BANDING_HINTS, data, sz,
                      AVM_MIF_ANY_FRAME);

  return sz;
}

// Helper function to read banding hints from a bit buffer (short metadata path)
static void read_metadata_banding_hints_from_rb(
    AV2Decoder *const pbi, struct avm_read_bit_buffer *rb) {
  avm_banding_hints_metadata_t *md = &pbi->band_metadata;
  memset(md, 0, sizeof(*md));

  md->coding_banding_present_flag = avm_rb_read_bit(rb);
  md->source_banding_present_flag = avm_rb_read_bit(rb);

  if (md->coding_banding_present_flag) {
    md->banding_hints_flag = avm_rb_read_bit(rb);

    if (md->banding_hints_flag) {
      md->three_color_components = avm_rb_read_bit(rb);
      const int num_components = md->three_color_components ? 3 : 1;

      for (int plane = 0; plane < num_components; plane++) {
        md->banding_in_component_present_flag[plane] = avm_rb_read_bit(rb);
        if (md->banding_in_component_present_flag[plane]) {
          md->max_band_width_minus4[plane] = avm_rb_read_literal(rb, 6);
          md->max_band_step_minus1[plane] = avm_rb_read_literal(rb, 4);
        }
      }

      md->band_units_information_present_flag = avm_rb_read_bit(rb);
      if (md->band_units_information_present_flag) {
        md->num_band_units_rows_minus_1 = avm_rb_read_literal(rb, 5);
        md->num_band_units_cols_minus_1 = avm_rb_read_literal(rb, 5);
        md->varying_size_band_units_flag = avm_rb_read_bit(rb);

        if (md->varying_size_band_units_flag) {
          md->band_block_in_luma_samples = avm_rb_read_literal(rb, 3);

          for (int r = 0; r <= md->num_band_units_rows_minus_1; r++) {
            md->vert_size_in_band_blocks_minus1[r] = avm_rb_read_literal(rb, 5);
          }

          for (int c = 0; c <= md->num_band_units_cols_minus_1; c++) {
            md->horz_size_in_band_blocks_minus1[c] = avm_rb_read_literal(rb, 5);
          }
        }

        for (int r = 0; r <= md->num_band_units_rows_minus_1; r++) {
          for (int c = 0; c <= md->num_band_units_cols_minus_1; c++) {
            md->banding_in_band_unit_present_flag[r][c] = avm_rb_read_bit(rb);
          }
        }
      }
    }
  }

  pbi->band_metadata_present = 1;

  // Re-encode to raw payload and store in metadata array
  uint8_t payload_buf[256];
  size_t payload_size = sizeof(payload_buf);
  if (avm_encode_banding_hints_metadata(md, payload_buf, &payload_size) == 0) {
    alloc_read_metadata(pbi, OBU_METADATA_TYPE_BANDING_HINTS, payload_buf,
                        payload_size, AVM_MIF_ANY_FRAME);
  }
}

// On success, returns the number of bytes read from 'data'. On failure, calls
// avm_internal_error() and does not return.
static size_t read_metadata_icc_profile(AV2Decoder *const pbi,
                                        const uint8_t *data, size_t sz) {
  const size_t kMinIccProfileHeaderSize = 128;
  AV2_COMMON *const cm = &pbi->common;
  if (sz < kMinIccProfileHeaderSize) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "Incorrect ICC profile metadata payload size");
  }
  alloc_read_metadata(pbi, OBU_METADATA_TYPE_ICC_PROFILE, data, sz,
                      AVM_MIF_ANY_FRAME);
  return sz;
}

// On failure, calls avm_internal_error() and does not return.
static void read_metadata_user_data_unregistered(AV2Decoder *const pbi,
                                                 const uint8_t *data,
                                                 size_t sz) {
  AV2_COMMON *const cm = &pbi->common;
  // uuid_iso_iec_11578 is 128 bits (16 bytes)
  const size_t uuid_size = 16;
  if (sz < uuid_size) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "uuid_iso_iec_11578 is missing or incomplete");
  }
  const int end_index = (int)sz;
  alloc_read_metadata(pbi, OBU_METADATA_TYPE_USER_DATA_UNREGISTERED, data,
                      end_index, AVM_MIF_ANY_FRAME);
}

// On success, returns the number of bytes read from 'data'. On failure, calls
// avm_internal_error() and does not return.
static void read_metadata_scan_type(AV2Decoder *const pbi,
                                    struct avm_read_bit_buffer *rb) {
  AV2_COMMON *const cm = &pbi->common;
  cm->pic_struct_metadata_params.mps_pic_struct_type =
      avm_rb_read_literal(rb, 5);
  cm->pic_struct_metadata_params.mps_source_scan_type_idc =
      avm_rb_read_literal(rb, 2);
  cm->pic_struct_metadata_params.mps_duplicate_flag = avm_rb_read_bit(rb);

  uint8_t payload[1];
  payload[0] = (cm->pic_struct_metadata_params.mps_pic_struct_type << 3) |
               (cm->pic_struct_metadata_params.mps_source_scan_type_idc << 1) |
               cm->pic_struct_metadata_params.mps_duplicate_flag;
  alloc_read_metadata(pbi, OBU_METADATA_TYPE_SCAN_TYPE, payload, 1,
                      AVM_MIF_ANY_FRAME);
}

// On success, returns the number of bytes read from 'data'. On failure, calls
// avm_internal_error() and does not return.
static void read_metadata_temporal_point_info(AV2Decoder *const pbi,
                                              struct avm_read_bit_buffer *rb) {
  AV2_COMMON *const cm = &pbi->common;
  cm->temporal_point_info_metadata.mtpi_frame_presentation_time =
      avm_rb_read_uleb(rb);
  uint8_t payload[1];
  payload[0] =
      (cm->temporal_point_info_metadata.mtpi_frame_presentation_time & 0XFF);
  alloc_read_metadata(pbi, OBU_METADATA_TYPE_TEMPORAL_POINT_INFO, payload, 1,
                      AVM_MIF_ANY_FRAME);
}

static int read_metadata_frame_hash(AV2Decoder *const pbi,
                                    struct avm_read_bit_buffer *rb) {
  AV2_COMMON *const cm = &pbi->common;
  const unsigned hash_type = avm_rb_read_literal(rb, 4);
  const unsigned per_plane = avm_rb_read_bit(rb);
  const unsigned has_grain = avm_rb_read_bit(rb);
  const unsigned is_monochrome = avm_rb_read_bit(rb);
  avm_rb_read_literal(rb, 1);  // reserved

  // If hash_type is reserved for future use, ignore the entire OBU
  if (hash_type) return -1;

  FrameHash *const frame_hash = has_grain ? &cm->cur_frame->grain_frame_hash
                                          : &cm->cur_frame->raw_frame_hash;
  memset(frame_hash, 0, sizeof(*frame_hash));

  frame_hash->hash_type = hash_type;
  frame_hash->per_plane = per_plane;
  frame_hash->has_grain = has_grain;
  if (per_plane) {
    const int num_planes = is_monochrome ? 1 : 3;
    for (int i = 0; i < num_planes; ++i) {
      PlaneHash *plane = &frame_hash->plane[i];
      for (size_t j = 0; j < 16; ++j)
        plane->md5[j] = avm_rb_read_literal(rb, 8);
    }
  } else {
    PlaneHash *plane = &frame_hash->plane[0];
    for (size_t i = 0; i < 16; ++i) plane->md5[i] = avm_rb_read_literal(rb, 8);
  }
  frame_hash->is_present = 1;

  return 0;
}

static void read_metadata_timecode(struct avm_read_bit_buffer *rb) {
  avm_rb_read_literal(rb, 5);  // counting_type f(5)
  const int full_timestamp_flag =
      avm_rb_read_bit(rb);     // full_timestamp_flag f(1)
  avm_rb_read_bit(rb);         // discontinuity_flag (f1)
  avm_rb_read_bit(rb);         // cnt_dropped_flag f(1)
  avm_rb_read_literal(rb, 9);  // n_frames f(9)
  if (full_timestamp_flag) {
    avm_rb_read_literal(rb, 6);  // seconds_value f(6)
    avm_rb_read_literal(rb, 6);  // minutes_value f(6)
    avm_rb_read_literal(rb, 5);  // hours_value f(5)
  } else {
    const int seconds_flag = avm_rb_read_bit(rb);  // seconds_flag f(1)
    if (seconds_flag) {
      avm_rb_read_literal(rb, 6);                    // seconds_value f(6)
      const int minutes_flag = avm_rb_read_bit(rb);  // minutes_flag f(1)
      if (minutes_flag) {
        avm_rb_read_literal(rb, 6);                  // minutes_value f(6)
        const int hours_flag = avm_rb_read_bit(rb);  // hours_flag f(1)
        if (hours_flag) {
          avm_rb_read_literal(rb, 5);  // hours_value f(5)
        }
      }
    }
  }
  // time_offset_length f(5)
  const int time_offset_length = avm_rb_read_literal(rb, 5);
  if (time_offset_length) {
    // time_offset_value f(time_offset_length)
    avm_rb_read_literal(rb, time_offset_length);
  }
}

// Returns the last nonzero byte in 'data'. If there is no nonzero byte in
// 'data', returns 0.
//
// Call this function to check the following requirement in the spec:
//   This implies that when any payload data is present for this OBU type, at
//   least one byte of the payload data (including the trailing bit) shall not
//   be equal to 0.
static uint8_t get_last_nonzero_byte(const uint8_t *data, size_t sz) {
  // Scan backward and return on the first nonzero byte.
  size_t i = sz;
  while (i != 0) {
    --i;
    if (data[i] != 0) return data[i];
  }
  return 0;
}

// Skip metadata_unit_remaining_bits: decoders conforming to this version of
// the specification shall ignore metadata_unit_remaining_bits.
static void skip_remaining_mu_payload_bits(struct avm_read_bit_buffer *rb,
                                           size_t parsed_payload_bits,
                                           size_t total_payload_bits) {
  if (parsed_payload_bits < total_payload_bits) {
    size_t remaining_bits = total_payload_bits - parsed_payload_bits;
    while (remaining_bits > 0) {
      const int chunk = (remaining_bits > 31) ? 31 : (int)remaining_bits;
      avm_rb_read_literal(rb, chunk);
      remaining_bits -= chunk;
    }
  }
}

// Checks the metadata for correct syntax but ignores the parsed metadata.
//
// On success, returns the number of bytes read from 'data'. On failure, sets
// pbi->common.error.error_code and returns 0, or calls avm_internal_error()
// and does not return.
static size_t read_metadata_unit_payload(AV2Decoder *pbi, const uint8_t *data,
                                         avm_metadata_t *metadata) {
  AV2_COMMON *const cm = &pbi->common;
  size_t type_length = 0;
  const OBU_METADATA_TYPE metadata_type = metadata->type;
  const size_t sz = metadata->sz;

  int known_metadata_type = metadata_type >= OBU_METADATA_TYPE_HDR_CLL &&
                            metadata_type < NUM_OBU_METADATA_TYPES;
  known_metadata_type |= metadata_type == OBU_METADATA_TYPE_ICC_PROFILE;
  known_metadata_type |= metadata_type == OBU_METADATA_TYPE_SCAN_TYPE;
  known_metadata_type |= metadata_type == OBU_METADATA_TYPE_TEMPORAL_POINT_INFO;
  known_metadata_type |=
      metadata_type == OBU_METADATA_TYPE_USER_DATA_UNREGISTERED;
  if (!known_metadata_type) {
    return sz;
  }
  // Temporal point info metadata is only valid in SHORT format, not GROUP.
  if (metadata_type == OBU_METADATA_TYPE_TEMPORAL_POINT_INFO) {
    avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                       "Temporal point info metadata shall only appear in "
                       "OBU_METADATA_SHORT, not OBU_METADATA_GROUP");
  }
  // Track bits consumed by the type-specific metadata reader, matching the
  // proposal's parsedPayloadBits = currentPosition - startPosition.
  size_t parsed_payload_bits = 0;
  struct avm_read_bit_buffer rb;
  if (metadata_type == OBU_METADATA_TYPE_ITUT_T35) {
    read_metadata_itut_t35(pbi, data + type_length, sz - type_length);
    parsed_payload_bits = sz * 8;
  } else if (metadata_type == OBU_METADATA_TYPE_HDR_CLL) {
    read_metadata_hdr_cll(pbi, data + type_length, sz - type_length);
    parsed_payload_bits = sz * 8;
  } else if (metadata_type == OBU_METADATA_TYPE_HDR_MDCV) {
    read_metadata_hdr_mdcv(pbi, data + type_length, sz - type_length);
    parsed_payload_bits = sz * 8;
  } else if (metadata_type == OBU_METADATA_TYPE_BANDING_HINTS) {
    read_metadata_banding_hints(pbi, data + type_length, sz - type_length);
    parsed_payload_bits = sz * 8;
  } else if (metadata_type == OBU_METADATA_TYPE_SCAN_TYPE) {
    av2_init_read_bit_buffer(pbi, &rb, data + type_length, data + sz);
    read_metadata_scan_type(pbi, &rb);
    parsed_payload_bits = rb.bit_offset;
  } else if (metadata_type == OBU_METADATA_TYPE_TEMPORAL_POINT_INFO) {
    av2_init_read_bit_buffer(pbi, &rb, data + type_length, data + sz);
    read_metadata_temporal_point_info(pbi, &rb);
    parsed_payload_bits = rb.bit_offset;
  } else if (metadata_type == OBU_METADATA_TYPE_ICC_PROFILE) {
    read_metadata_icc_profile(pbi, data + type_length, sz - type_length);
    parsed_payload_bits = sz * 8;
  } else if (metadata_type == OBU_METADATA_TYPE_USER_DATA_UNREGISTERED) {
    read_metadata_user_data_unregistered(pbi, data + type_length,
                                         sz - type_length);
    parsed_payload_bits = sz * 8;
  } else {
    // Remaining types: TIMECODE, DECODED_FRAME_HASH, SCALABILITY.
    av2_init_read_bit_buffer(pbi, &rb, data + type_length, data + sz);
    if (metadata_type == OBU_METADATA_TYPE_DECODED_FRAME_HASH) {
      if (read_metadata_frame_hash(pbi, &rb)) {
        return sz;
      }
    } else {
      assert(metadata_type == OBU_METADATA_TYPE_TIMECODE);
      read_metadata_timecode(&rb);
    }
    parsed_payload_bits = rb.bit_offset;
  }

  // Compute remaining payload bits and skip them.
  const size_t total_payload_bits = (sz - type_length) * 8;
  av2_init_read_bit_buffer(pbi, &rb, data + type_length, data + sz);
  rb.bit_offset = (uint32_t)parsed_payload_bits;
  skip_remaining_mu_payload_bits(&rb, parsed_payload_bits, total_payload_bits);

  return sz;
}

static size_t read_metadata_obsp(AV2Decoder *pbi, const uint8_t *data,
                                 size_t sz,
                                 avm_metadata_array_t *metadata_array,
                                 avm_metadata_t *metadata_base,
                                 int expected_suffix) {
  AV2_COMMON *const cm = &pbi->common;

  struct avm_read_bit_buffer rb;
  av2_init_read_bit_buffer(pbi, &rb, data, data + sz);

  metadata_base->is_suffix = avm_rb_read_literal(&rb, 1);

  // Validate suffix bit if requested
  if (expected_suffix >= 0 && metadata_base->is_suffix != expected_suffix) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }

  metadata_base->necessity_idc =
      (avm_metadata_necessity_t)avm_rb_read_literal(&rb, 2);
  metadata_base->application_id =
      (avm_metadata_application_id_t)avm_rb_read_literal(&rb, 5);

  const size_t bytes_read = avm_rb_bytes_read(&rb);
  assert(bytes_read == 1);

  size_t count_length;
  uint64_t count_minus_1;
  if (avm_uleb_decode(data + bytes_read, sz - bytes_read, &count_minus_1,
                      &count_length) < 0) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }

  metadata_array->sz = count_minus_1 + 1;

  // Ensure metadata_unit_cnt doesn't exceed 2^14 (uleb128 <= 2 bytes)
  if (metadata_array->sz > 16384) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }

  return bytes_read + count_length;
}

static size_t read_metadata_unit_header(AV2Decoder *pbi, const uint8_t *data,
                                        size_t sz, avm_metadata_t *metadata,
                                        const ObuHeader *obu_header) {
  AV2_COMMON *const cm = &pbi->common;
  size_t type_length;
  uint64_t type_value;
  if (avm_uleb_decode(data, sz, &type_value, &type_length) < 0 ||
      type_value > UINT32_MAX) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }
  metadata->type = (uint32_t)type_value;
  size_t bytes_read = type_length;

  struct avm_read_bit_buffer rb;
  av2_init_read_bit_buffer(pbi, &rb, data + bytes_read, data + sz);

  const size_t muh_header_size = avm_rb_read_literal(&rb, 7);
  metadata->cancel_flag = avm_rb_read_literal(&rb, 1);
  assert(avm_rb_bytes_read(&rb) == 1);
  bytes_read += avm_rb_bytes_read(&rb);

  const size_t total_size = bytes_read + muh_header_size;
  if (total_size > sz) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }

  if (!metadata->cancel_flag) {
    size_t size_length;
    uint64_t size_value = 0;
    if (avm_uleb_decode(data + bytes_read, total_size - bytes_read, &size_value,
                        &size_length) < 0) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
    metadata->sz = size_value;
    bytes_read += size_length;

    av2_init_read_bit_buffer(pbi, &rb, data + bytes_read, data + total_size);

    metadata->layer_idc = (avm_metadata_layer_t)avm_rb_read_literal(&rb, 3);
    metadata->persistence_idc =
        (avm_metadata_persistence_t)avm_rb_read_literal(&rb, 3);
    metadata->priority = avm_rb_read_literal(&rb, 8);
    avm_rb_read_literal(&rb, 2);  // reserved bits

    assert(avm_rb_bytes_read(&rb) == 2);

    if (metadata->layer_idc == AVM_LAYER_VALUES) {
      if (obu_header->obu_xlayer_id == 31) {
        metadata->xlayer_map = avm_rb_read_unsigned_literal(&rb, 32);
        if ((metadata->xlayer_map & (1u << 31)) != 0) {
          cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
          return 0;
        }
        for (int n = 0; n < 31; n++) {
          if (metadata->xlayer_map & (1u << n)) {
            metadata->mlayer_map[n] = avm_rb_read_unsigned_literal(&rb, 8);
          }
        }
      } else {
        metadata->mlayer_map[obu_header->obu_xlayer_id] =
            avm_rb_read_unsigned_literal(&rb, 8);
      }
    }

    bytes_read += avm_rb_bytes_read(&rb);
  }

  assert(bytes_read <= total_size);
  return total_size;
}

static size_t read_metadata_obu(AV2Decoder *pbi, const uint8_t *data, size_t sz,
                                ObuHeader *obu_header, int expected_suffix) {
  AV2_COMMON *const cm = &pbi->common;

  avm_metadata_array_t metadata_array = { 0 };
  avm_metadata_t metadata_base;
  memset(&metadata_base, 0, sizeof(metadata_base));
  size_t bytes_read = read_metadata_obsp(pbi, data, sz, &metadata_array,
                                         &metadata_base, expected_suffix);

  for (uint32_t i = 0; i < metadata_array.sz; i++) {
    avm_metadata_t metadata = { 0 };
    // copy shared fields read in `read_metadata_obsp`
    memcpy(&metadata, &metadata_base, sizeof(metadata));

    const size_t muh_size = read_metadata_unit_header(
        pbi, data + bytes_read, sz - bytes_read, &metadata, obu_header);
    if (muh_size == 0) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
    bytes_read += muh_size;
    if (!metadata.cancel_flag) {
      if (sz - bytes_read < metadata.sz) {
        cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
        return 0;
      }
      const size_t mup_size =
          read_metadata_unit_payload(pbi, data + bytes_read, &metadata);
      bytes_read += mup_size;
    }
  }

  if (bytes_read >= sz || data[bytes_read] != 0x80) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }
  return bytes_read + 1;
}

// Checks the metadata for correct syntax but ignores the parsed metadata.
//
// On success, returns the number of bytes read from 'data'. On failure, sets
// pbi->common.error.error_code and returns 0, or calls avm_internal_error()
// and does not return.
// expected_suffix: 0 for prefix metadata, 1 for suffix metadata, -1 for no
// validation
static size_t read_metadata_short(AV2Decoder *pbi, const uint8_t *data,
                                  size_t sz, int expected_suffix) {
  AV2_COMMON *const cm = &pbi->common;
  size_t type_length;
  uint64_t type_value;
  struct avm_read_bit_buffer rb;
  av2_init_read_bit_buffer(pbi, &rb, data, data + sz);

  uint8_t metadata_is_suffix = avm_rb_read_bit(&rb);

  // Validate suffix bit if requested
  if (expected_suffix >= 0 && metadata_is_suffix != expected_suffix) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }

  uint8_t muh_layer_idc = avm_rb_read_literal(&rb, 3);
  uint8_t muh_cancel_flag = avm_rb_read_bit(&rb);
  uint8_t muh_persistence_idc = avm_rb_read_literal(&rb, 3);
  if (avm_uleb_decode(
          data + 1,  // read type from the position data + 1
          sz - 1,    // one less bytes available due to extra parameters
          &type_value, &type_length) < 0) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return 0;
  }

  const OBU_METADATA_TYPE metadata_type = (OBU_METADATA_TYPE)type_value;

  // Increase the type_length by 1 byte since there is one prefix byte added
  // before the type
  ++type_length;
  if (muh_cancel_flag) return sz;

  // Update the metadata with the header fields we read
  if (pbi->metadata && pbi->metadata->sz > 0) {
    avm_metadata_t *last_metadata =
        pbi->metadata->metadata_array[pbi->metadata->sz - 1];
    if (last_metadata && last_metadata->type == OBU_METADATA_TYPE_ITUT_T35) {
      last_metadata->is_suffix = metadata_is_suffix;
      last_metadata->layer_idc = muh_layer_idc;
      last_metadata->cancel_flag = muh_cancel_flag;
      last_metadata->persistence_idc = muh_persistence_idc;
    }
  }

  const bool known_metadata_type =
      (metadata_type > OBU_METADATA_TYPE_AVM_RESERVED_0) &&
      (metadata_type < NUM_OBU_METADATA_TYPES);
  if (!known_metadata_type) {
    // If metadata_type is reserved for future use or a user private value,
    // ignore the entire OBU and just check trailing bits.
    if (get_last_nonzero_byte(data + type_length, sz - type_length) == 0) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
    return sz;
  }
  if (metadata_type == OBU_METADATA_TYPE_ITUT_T35) {
    // read_metadata_itut_t35() checks trailing bits.
    read_metadata_itut_t35_short(pbi, data + type_length, sz - type_length);
    // Update the metadata with the header fields we read
    if (pbi->metadata && pbi->metadata->sz > 0) {
      avm_metadata_t *last_metadata =
          pbi->metadata->metadata_array[pbi->metadata->sz - 1];
      if (last_metadata && last_metadata->type == OBU_METADATA_TYPE_ITUT_T35) {
        last_metadata->is_suffix = metadata_is_suffix;
        last_metadata->layer_idc = muh_layer_idc;
        last_metadata->cancel_flag = muh_cancel_flag;
        last_metadata->persistence_idc = muh_persistence_idc;
      }
    }
    return sz;
  } else if (metadata_type == OBU_METADATA_TYPE_HDR_CLL) {
    size_t bytes_read =
        type_length +
        read_metadata_hdr_cll(pbi, data + type_length, sz - type_length);
    if (get_last_nonzero_byte(data + bytes_read, sz - bytes_read) != 0x80) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
    // Update the metadata with the header fields we read
    if (pbi->metadata && pbi->metadata->sz > 0) {
      avm_metadata_t *last_metadata =
          pbi->metadata->metadata_array[pbi->metadata->sz - 1];
      if (last_metadata && last_metadata->type == OBU_METADATA_TYPE_HDR_CLL) {
        last_metadata->is_suffix = metadata_is_suffix;
        last_metadata->layer_idc = muh_layer_idc;
        last_metadata->cancel_flag = muh_cancel_flag;
        last_metadata->persistence_idc = muh_persistence_idc;
      }
    }
    return sz;
  } else if (metadata_type == OBU_METADATA_TYPE_HDR_MDCV) {
    size_t bytes_read =
        type_length +
        read_metadata_hdr_mdcv(pbi, data + type_length, sz - type_length);
    if (get_last_nonzero_byte(data + bytes_read, sz - bytes_read) != 0x80) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
    // Update the metadata with the header fields we read
    if (pbi->metadata && pbi->metadata->sz > 0) {
      avm_metadata_t *last_metadata =
          pbi->metadata->metadata_array[pbi->metadata->sz - 1];
      if (last_metadata && last_metadata->type == OBU_METADATA_TYPE_HDR_MDCV) {
        last_metadata->is_suffix = metadata_is_suffix;
        last_metadata->layer_idc = muh_layer_idc;
        last_metadata->cancel_flag = muh_cancel_flag;
        last_metadata->persistence_idc = muh_persistence_idc;
      }
    }
    return sz;
  } else if (metadata_type == OBU_METADATA_TYPE_SCAN_TYPE) {
    const size_t kMinScanTypeHeaderSize = 1;
    if (sz < kMinScanTypeHeaderSize) {
      avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                         "Incorrect scan type metadata payload size");
    }
    av2_init_read_bit_buffer(pbi, &rb, data + type_length, data + sz);
    read_metadata_scan_type(pbi, &rb);
    return sz;
  } else if (metadata_type == OBU_METADATA_TYPE_TEMPORAL_POINT_INFO) {
    const size_t kMinTemporalPointInfoHeaderSize = 1;
    if (sz < kMinTemporalPointInfoHeaderSize) {
      avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                         "Incorrect temporal point info metadata payload size");
    }
    av2_init_read_bit_buffer(pbi, &rb, data + type_length, data + sz);
    read_metadata_temporal_point_info(pbi, &rb);
    return sz;
  }

  av2_init_read_bit_buffer(pbi, &rb, data + type_length, data + sz);
  if (metadata_type == OBU_METADATA_TYPE_DECODED_FRAME_HASH) {
    if (read_metadata_frame_hash(pbi, &rb)) {
      // Unsupported Decoded Frame Hash metadata. Ignoring the entire OBU and
      // just checking trailing bits
      if (get_last_nonzero_byte(data + type_length, sz - type_length) == 0) {
        cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
        return 0;
      }
      return sz;
    }
  } else if (metadata_type == OBU_METADATA_TYPE_BANDING_HINTS) {
    // Banding hints metadata is variable bits, not byte-aligned
    read_metadata_banding_hints_from_rb(pbi, &rb);
    // Update the metadata with the header fields we read
    if (pbi->metadata && pbi->metadata->sz > 0) {
      avm_metadata_t *last_metadata =
          pbi->metadata->metadata_array[pbi->metadata->sz - 1];
      if (last_metadata &&
          last_metadata->type == OBU_METADATA_TYPE_BANDING_HINTS) {
        last_metadata->is_suffix = metadata_is_suffix;
        last_metadata->layer_idc = muh_layer_idc;
        last_metadata->cancel_flag = muh_cancel_flag;
        last_metadata->persistence_idc = muh_persistence_idc;
      }
    }
  } else if (metadata_type == OBU_METADATA_TYPE_ICC_PROFILE) {
    // ICC profile is byte-aligned binary data
    // Find the last nonzero byte (should be 0x80 trailing byte)
    const int last_nonzero_idx =
        get_last_nonzero_byte_index(data + type_length, sz - type_length);
    if (last_nonzero_idx < 0 || data[type_length + last_nonzero_idx] != 0x80) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
    // ICC payload size excludes the trailing 0x80 byte
    const size_t icc_payload_size = last_nonzero_idx;
    read_metadata_icc_profile(pbi, data + type_length, icc_payload_size);
    return sz;
  } else if (metadata_type == OBU_METADATA_TYPE_USER_DATA_UNREGISTERED) {
    // User data unregistered is byte-aligned binary data
    // Find the last nonzero byte (should be 0x80 trailing byte)
    const int last_nonzero_idx =
        get_last_nonzero_byte_index(data + type_length, sz - type_length);
    if (last_nonzero_idx < 0 || data[type_length + last_nonzero_idx] != 0x80) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
    // User data payload size excludes the trailing 0x80 byte
    const size_t user_data_payload_size = last_nonzero_idx;
    read_metadata_user_data_unregistered(pbi, data + type_length,
                                         user_data_payload_size);
    return sz;
  } else {
    assert(metadata_type == OBU_METADATA_TYPE_TIMECODE);
    read_metadata_timecode(&rb);
  }
  {
    // Compute remaining payload bits and skip them.
    // Subtract 1 from sz to exclude the trailing 0x80 byte, which is part of
    // metadata_short_obu() trailing_bits() syntax, not metadata_unit() payload.
    const size_t parsed_payload_bits = rb.bit_offset;
    const size_t total_payload_bits = (sz - type_length - 1) * 8;
    skip_remaining_mu_payload_bits(&rb, parsed_payload_bits,
                                   total_payload_bits);
  }
  if (av2_check_trailing_bits(pbi, &rb) != 0) {
    // cm->error.error_code is already set.
    return 0;
  }
  assert((rb.bit_offset & 7) == 0);
  return type_length + (rb.bit_offset >> 3);
}
// On success, returns 'sz'. On failure, sets pbi->common.error.error_code and
// returns 0.
static size_t read_padding(AV2_COMMON *const cm, const uint8_t *data,
                           size_t sz) {
  // The spec allows a padding OBU to be header-only (i.e., obu_size = 0). So
  // check trailing bits only if sz > 0.
  if (sz > 0) {
    // The payload of a padding OBU is byte aligned. Therefore the first
    // trailing byte should be 0x80. See https://crbug.com/aomedia/2393.
    const uint8_t last_nonzero_byte = get_last_nonzero_byte(data, sz);
    if (last_nonzero_byte != 0x80) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return 0;
    }
  }
  return sz;
}

int av2_is_leading_vcl_obu(OBU_TYPE obu_type) {
  return (obu_type == OBU_LEADING_TILE_GROUP || obu_type == OBU_LEADING_SEF ||
          obu_type == OBU_LEADING_TIP);
}

// Check if any obu is present between two tile groups of one frame unit.
static void check_tilegroup_obus_in_a_frame_unit(AV2_COMMON *const cm,
                                                 obu_info *current_obu,
                                                 obu_info *prev_obu) {
  if (current_obu->obu_type != prev_obu->obu_type ||
      current_obu->immediate_output_picture !=
          prev_obu->immediate_output_picture ||
      current_obu->showable_frame != prev_obu->showable_frame ||
      current_obu->display_order_hint != prev_obu->display_order_hint ||
      current_obu->mlayer_id != prev_obu->mlayer_id) {
    avm_internal_error(
        &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
        "%s : no non-padding obu is allowed between tilegroup obus in a frame "
        "unit (current obu %s, current oh %d previous obu %s previous oh %d)",
        __func__, avm_obu_type_to_string(current_obu->obu_type),
        current_obu->display_order_hint,
        avm_obu_type_to_string(prev_obu->obu_type),
        prev_obu->display_order_hint);
  }
}

// TU validation: Check if an OBU type is metadata obu
static int is_metadata_obu(OBU_TYPE obu_type) {
  return (obu_type == OBU_METADATA_SHORT || obu_type == OBU_METADATA_GROUP);
}

// TU validation: Check if an OBU type is global configuration information
static int is_global_config_obu(OBU_TYPE obu_type, int xlayer_id) {
  return obu_type == OBU_MULTI_STREAM_DECODER_OPERATION ||
         (obu_type == OBU_LAYER_CONFIGURATION_RECORD &&
          xlayer_id == GLOBAL_XLAYER_ID) ||
         (obu_type == OBU_OPERATING_POINT_SET &&
          xlayer_id == GLOBAL_XLAYER_ID) ||
         (obu_type == OBU_ATLAS_SEGMENT && xlayer_id == GLOBAL_XLAYER_ID) ||
         (obu_type == OBU_METADATA_SHORT && xlayer_id == GLOBAL_XLAYER_ID) ||
         (obu_type == OBU_METADATA_GROUP && xlayer_id == GLOBAL_XLAYER_ID);
}

// TU validation: Check if an OBU type is local configuration information
static int is_local_config_obu(OBU_TYPE obu_type, int xlayer_id) {
  return (obu_type == OBU_LAYER_CONFIGURATION_RECORD &&
          xlayer_id != GLOBAL_XLAYER_ID) ||
         (obu_type == OBU_OPERATING_POINT_SET &&
          xlayer_id != GLOBAL_XLAYER_ID) ||
         (obu_type == OBU_ATLAS_SEGMENT && xlayer_id != GLOBAL_XLAYER_ID);
}

// TU validation: Check if an OBU type is not global or local configuration
// information, not sequence header or not padding
static int is_frame_unit(OBU_TYPE obu_type, int xlayer_id) {
  //  OBU_MULTI_FRAME_HEADER,
  //  OBU_BUFFER_REMOVAL_TIMING,
  //  OBU_QUANTIZATION_MATRIX,
  //  OBU_FILM_GRAIN_MODEL,
  //  OBU_CONTENT_INTERPRETATION,
  //  OBU_METADATA_SHORT,
  //  OBU_METADATA_GROUP,
  //  OBU_CLOSED_LOOP_KEY,
  //  OBU_OPEN_LOOP_KEY,
  //  OBU_LEADING_TILE_GROUP,
  //  OBU_REGULAR_TILE_GROUP,
  //  OBU_SWITCH,
  //  OBU_LEADING_SEF,
  //  OBU_REGULAR_SEF,
  //  OBU_LEADING_TIP,
  //  OBU_REGULAR_TIP,
  //  OBU_BRIDGE_FRAME,
  //  OBU_RAS_FRAME
  int non_frame_unit_obu = is_global_config_obu(obu_type, xlayer_id) ||
                           is_local_config_obu(obu_type, xlayer_id) ||
                           obu_type == OBU_SEQUENCE_HEADER ||
                           obu_type == OBU_PADDING;

  return !non_frame_unit_obu;
}

static int is_coded_frame(OBU_TYPE obu_type) {
  return is_multi_tile_vcl_obu(obu_type) || is_single_tile_vcl_obu(obu_type);
}
// Validates OBU order within a Temporal Unit with state machine.
// Returns 1 if OBU is valid for the current state, 0 if it violates rules.
// Note: The caller is responsible for filtering out padding OBUs and reserved
// OBUs before calling this function.
//
// After the global information phase, all remaining OBUs are grouped by
// xlayer_id in strictly increasing order. Within each xlayer group the full
// phase progression is enforced:
//   local config (LCR -> OPS -> Atlas) -> SH -> frame unit data
// When the xlayer_id increases, the state resets to TU_STATE_LOCAL_INFO so
// the new xlayer group may begin with local config, SH, or frame unit data.
int check_temporal_unit_structure(temporal_unit_state_t *state, int obu_type,
                                  int xlayer_id, int metadata_is_suffix,
                                  int prev_obu_type, int prev_xlayer_id) {
  // Validate input parameters
  if (!state) return 0;

  // After the global phase, detect an xlayer_id increase and reset the
  // per-xlayer state machine so the new group starts fresh.
  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 &&
        xlayer_id != prev_xlayer_id) {
      if (xlayer_id < prev_xlayer_id) return 0;  // Must be increasing
      // New xlayer group: reset to local info phase.
      *state = TU_STATE_LOCAL_INFO;
    }
  }

  switch (*state) {
    case TU_STATE_START:
    case TU_STATE_TEMPORAL_DELIMITER:
      if (obu_type == OBU_TEMPORAL_DELIMITER) {
        if (*state == TU_STATE_TEMPORAL_DELIMITER)
          return 0;  // Only one allowed
        *state = TU_STATE_TEMPORAL_DELIMITER;
        return 1;
      } else if (is_global_config_obu(obu_type,
                                      xlayer_id)) {  // First OBU: global config
        *state = TU_STATE_GLOBAL_INFO;
        return 1;
      } else if (is_local_config_obu(obu_type,
                                     xlayer_id)) {  // First OBU: local config
        *state = TU_STATE_LOCAL_INFO;
        return 1;
      } else if (obu_type == OBU_SEQUENCE_HEADER) {
        *state = TU_STATE_SEQUENCE_HEADER;
        return 1;
      } else if (is_frame_unit(obu_type, xlayer_id)) {
        *state = TU_STATE_FRAME_UINT_DATA;
        return 1;
      } else {  // Invalid OBU type for start of temporal unit
        return 0;
      }

    case TU_STATE_GLOBAL_INFO:
      if (is_global_config_obu(obu_type, xlayer_id)) {
        // 0 or 1 OBU_MULTI_STREAM_DECODER_OPERATION,
        // 0 or more: OBU_LAYER_CONFIGURATION_RECORD
        // 0 or more: OBU_OPERATING_POINT_SET
        // 0 or more: OBU_ATLAS_SEGMENT
        // 0 or more: OBU_METADATA(obu_xlayer_id = 0x1F)
        // MSDO -> LCR -> LCR -> OPS -> OPS -> ATS -> ATS -> METADATA ->
        // METADATA
        if (obu_type == OBU_MULTI_STREAM_DECODER_OPERATION)
          return 0;  // Only one allowed
        else if (obu_type == OBU_LAYER_CONFIGURATION_RECORD &&
                 (prev_obu_type == OBU_OPERATING_POINT_SET ||
                  prev_obu_type == OBU_ATLAS_SEGMENT ||
                  is_metadata_obu(prev_obu_type))) {
          return 0;
        } else if (obu_type == OBU_OPERATING_POINT_SET &&
                   (prev_obu_type == OBU_ATLAS_SEGMENT ||
                    is_metadata_obu(prev_obu_type))) {
          return 0;
        } else if (obu_type == OBU_ATLAS_SEGMENT &&
                   is_metadata_obu(prev_obu_type)) {
          return 0;
        } else
          return 1;
      } else if (is_local_config_obu(obu_type, xlayer_id)) {
        *state = TU_STATE_LOCAL_INFO;  // Transition from global to local
        return 1;
      } else if (obu_type == OBU_SEQUENCE_HEADER) {
        *state = TU_STATE_SEQUENCE_HEADER;
        return 1;
      } else if (is_frame_unit(obu_type, xlayer_id)) {
        *state = TU_STATE_FRAME_UINT_DATA;
        return 1;
      } else {
        // Invalid OBU type during global info phase or wrong xlayer_id
        return 0;
      }

    case TU_STATE_LOCAL_INFO:
      if (is_local_config_obu(obu_type, xlayer_id)) {
        // Within the same xlayer group: LCR -> OPS -> Atlas (no going back).
        if (xlayer_id == prev_xlayer_id) {
          if (obu_type == OBU_LAYER_CONFIGURATION_RECORD &&
              prev_obu_type != OBU_LAYER_CONFIGURATION_RECORD) {
            return 0;
          } else if (obu_type == OBU_OPERATING_POINT_SET &&
                     prev_obu_type == OBU_ATLAS_SEGMENT) {
            return 0;
          }
        }
        // First OBU in a new xlayer group: any local config type is valid.
        return 1;
      } else if (obu_type == OBU_SEQUENCE_HEADER) {
        *state = TU_STATE_SEQUENCE_HEADER;
        return 1;
      } else if (is_frame_unit(obu_type, xlayer_id)) {
        *state = TU_STATE_FRAME_UINT_DATA;
        return 1;
      } else {
        return 0;  // Invalid OBU type(such as global obus) during local info
                   // phase
      }

    case TU_STATE_SEQUENCE_HEADER:
      if (obu_type == OBU_SEQUENCE_HEADER) {
        return 1;
      } else if (is_frame_unit(obu_type, xlayer_id)) {
        *state = TU_STATE_FRAME_UINT_DATA;
        return 1;
      } else {
        return 0;  // Invalid OBU type(such as global obus) during sequence
                   // header phase
      }

    case TU_STATE_FRAME_UINT_DATA:
      if (is_frame_unit(obu_type, xlayer_id)) {
        // Within the same xlayer group: enforce existing type ordering.
        // CI -> CI -> MFH -> MFH -> (BRT, QM, FGM, METADATA_prefix,
        // METADATA_SHORT_prefix) -> coded_frame -> METADATA_suffix,
        // METADATA_SHORT_suffix)
        if (obu_type == OBU_CONTENT_INTERPRETATION &&
            prev_obu_type != OBU_CONTENT_INTERPRETATION)
          return 0;
        else if (obu_type == OBU_MULTI_FRAME_HEADER &&
                 (prev_obu_type != OBU_CONTENT_INTERPRETATION &&
                  prev_obu_type != OBU_MULTI_FRAME_HEADER))
          return 0;
        else if (((is_metadata_obu(obu_type) &&
                   metadata_is_suffix == 0) ||  // prefix
                  obu_type == OBU_BUFFER_REMOVAL_TIMING ||
                  obu_type == OBU_QUANTIZATION_MATRIX ||
                  obu_type == OBU_FILM_GRAIN_MODEL) &&
                 (is_coded_frame(prev_obu_type) ||
                  (is_metadata_obu(prev_obu_type) &&
                   metadata_is_suffix == 1)))  // suffix
          return 0;
        else  // other cases may be evaluated later
          return 1;
      } else {
        return 0;
      }

    default:
      // Invalid state
      return 0;
  }
}

// Check if the CLK is the first frame of a mlayer.
static void check_clk_in_a_layer(AV2_COMMON *const cm,
                                 obu_info *current_frame_unit,
                                 obu_info *last_frame_unit) {
  if (current_frame_unit->obu_type == OBU_CLOSED_LOOP_KEY &&
      last_frame_unit->obu_type != OBU_CLOSED_LOOP_KEY &&
      current_frame_unit->mlayer_id == last_frame_unit->mlayer_id &&
      current_frame_unit->display_order_hint ==
          last_frame_unit->display_order_hint) {
    avm_internal_error(
        &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
        "%s : a CLK should be the first frame of a mlayer. "
        "current obu %s, current oh "
        "%d, current mlayer_id %d, "
        "previous obu %s previous oh %d previous mlayer_id %d ",
        __func__, avm_obu_type_to_string(current_frame_unit->obu_type),
        current_frame_unit->display_order_hint, current_frame_unit->mlayer_id,
        avm_obu_type_to_string(last_frame_unit->obu_type),
        last_frame_unit->display_order_hint, last_frame_unit->mlayer_id);
  }
}

// Check the mlayer ids of frame units before the current hidden frame.
static void check_layerid_hidden_frame_units(AV2_COMMON *const cm,
                                             obu_info *current_frame_unit,
                                             obu_info *last_frame_unit) {
  //[H:layer0][H:layer1] not allowed
  //[H:layer1][H:layer1] checked later : [H:layer1][H:layer1][S:layer1] ok,
  //[H:layer1][H:layer1][S:layer0] not allowed [H:layer2][H:layer1] not allowed
  //[S:layer0][H:layer1] checked later:
  // 1) [S:layer0][H:layer1][S:layer1] maybe ok,
  // 2) [S:layer0][H:layer1][S:layer0] not allowed
  //[S:layer1][H:layer1] checked later :
  // 1) [S:layer1][H:layer1][S:layer1] maybe ok (e.g. CLK[0], Bridge[0], TG[1])
  // 2) [S:layer1][H:layer1][S:layer0] not allowed
  // 3) [S:layer1][H:layer1][S:layer2] not allowed
  //[S:layer2][H:layer1] allowed
  if ((last_frame_unit->showable_frame == 0 &&
       current_frame_unit->mlayer_id != last_frame_unit->mlayer_id)) {
    avm_internal_error(
        &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
        "%s : hidden frames should proceed displayable frames in a "
        "layer:\n\tcurrent  : "
        "(%s, OH%d, L%d, S%d)\n\t"
        "previous : (%s, OH%d, L%d, S%d)",
        __func__, avm_obu_type_to_string(current_frame_unit->obu_type),
        current_frame_unit->display_order_hint, current_frame_unit->mlayer_id,
        current_frame_unit->showable_frame,
        avm_obu_type_to_string(last_frame_unit->obu_type),
        last_frame_unit->display_order_hint, last_frame_unit->mlayer_id,
        last_frame_unit->showable_frame);
  }
}

// Check the mlayer ids of frame units before the current showable frame.
static void check_layerid_showable_frame_units(
    AV2_COMMON *const cm, obu_info *current_frame_unit,
    obu_info *last_frame_unit, obu_info *last_displayable_frame_unit) {
  //[H:layer0][*S:layer1] not allowed
  // 3) [S:layer1][H:layer1][*S:layer2] not allowed
  //[H:layer1][*S:layer1] check last displayable frame unit
  // 1) [S:layer0][H:layer1][*S:layer1] is allowed
  // 1) [S:layer1][H:layer1][*S:layer1] maybe okay - doh comparison required
  // betwee S and S
  //[H:layer2][*S:layer1] check last displayable frame unit
  // 2) [S:layer0][H:layer1][*S:layer0] not allowed
  // 2) [S:layer1][H:layer1][*S:layer0] not allowed

  //[S:layer0][*S:layer1] allowed
  //[S:layer1][*S:layer1] check orderhint of [S:layer1] and [S:layer1]
  //[S:layer2][*S:layer1] check orderhint of [S:layer2] and [S:layer1]

  if (last_frame_unit->showable_frame == 0 &&
      current_frame_unit->mlayer_id != last_frame_unit->mlayer_id) {
    avm_internal_error(
        &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
        "%s : hidden frames should proceed displayable frames in a "
        "layer:\n\tcurrent  : "
        "(%s, OH%d, L%d, S%d)\n\t"
        "previous : (%s, OH%d, L%d, S%d)",
        __func__, avm_obu_type_to_string(current_frame_unit->obu_type),
        current_frame_unit->display_order_hint, current_frame_unit->mlayer_id,
        current_frame_unit->showable_frame,
        avm_obu_type_to_string(last_frame_unit->obu_type),
        last_frame_unit->display_order_hint, last_frame_unit->mlayer_id,
        last_frame_unit->showable_frame);
  } else if (last_frame_unit->showable_frame == 0 &&
             current_frame_unit->mlayer_id == last_frame_unit->mlayer_id &&
             current_frame_unit->mlayer_id ==
                 last_displayable_frame_unit->mlayer_id) {
    if (current_frame_unit->display_order_hint ==
        last_displayable_frame_unit->display_order_hint) {
      avm_internal_error(
          &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
          "%s: mlayer_id should be in ascending order or order_hint should be "
          "different:\n"
          "\tcurrent  : (%s, OH%d, L%d, S%d)\n"
          "\tprevious : (%s, S%d)\n"
          "\tlast_displayable : (%s, OH%d, L%d)",
          __func__, avm_obu_type_to_string(current_frame_unit->obu_type),
          current_frame_unit->display_order_hint, current_frame_unit->mlayer_id,
          current_frame_unit->showable_frame,
          avm_obu_type_to_string(last_frame_unit->obu_type),
          last_frame_unit->showable_frame,
          avm_obu_type_to_string(last_displayable_frame_unit->obu_type),
          last_displayable_frame_unit->display_order_hint,
          last_displayable_frame_unit->mlayer_id);
    }
  } else if (last_frame_unit->showable_frame == 1 &&
             current_frame_unit->mlayer_id <= last_frame_unit->mlayer_id) {
    if (current_frame_unit->display_order_hint ==
        last_frame_unit->display_order_hint) {
      avm_internal_error(
          &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
          "%s: mlayer_id should be in ascending order or order_hint should be "
          "different:\n\tcurrent obu %s, current oh "
          "%d, current mlayer_id %d\n\t"
          "previous obu %s previous oh %d previous mlayer_id %d",
          __func__, avm_obu_type_to_string(current_frame_unit->obu_type),
          current_frame_unit->display_order_hint, current_frame_unit->mlayer_id,
          avm_obu_type_to_string(last_frame_unit->obu_type),
          last_frame_unit->display_order_hint, last_frame_unit->mlayer_id);
    }
  }
}

// Check xlayer_id, mlayer_id, and tlayer_id of the obu is valid for the
// obu_type. Reports with avm_internal_error.
static void check_valid_layer_id(ObuHeader obu_header, AV2_COMMON *const cm) {
  // Ignore reserved OBUs.
  if (!avm_obu_type_is_valid(obu_header.type)) return;

  if (obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID &&
      (obu_header.obu_tlayer_id != 0 || obu_header.obu_mlayer_id != 0)) {
    avm_internal_error(
        &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
        "Incorrect layer_id for %s: tlayer_id %d mlayer_id %d xlayer_id %d",
        avm_obu_type_to_string(obu_header.type), obu_header.obu_tlayer_id,
        obu_header.obu_mlayer_id, obu_header.obu_xlayer_id);
  }
  if (obu_header.type == OBU_MULTI_STREAM_DECODER_OPERATION &&
      obu_header.obu_xlayer_id != GLOBAL_XLAYER_ID) {
    avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                       "Incorrect obu_xlayer_id for MSDO: %d",
                       obu_header.obu_xlayer_id);
  }
  if (obu_header.type == OBU_SEQUENCE_HEADER ||
      obu_header.type == OBU_TEMPORAL_DELIMITER ||
      obu_header.type == OBU_LAYER_CONFIGURATION_RECORD ||
      obu_header.type == OBU_OPERATING_POINT_SET ||
      obu_header.type == OBU_ATLAS_SEGMENT) {
    if (obu_header.obu_tlayer_id != 0 || obu_header.obu_mlayer_id != 0)
      avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                         "Incorrect layer_id for %s: "
                         "tlayer_id %d mlayer_id %d",
                         avm_obu_type_to_string(obu_header.type),
                         obu_header.obu_tlayer_id, obu_header.obu_mlayer_id);
  }

  // MSDO, LCR, OPS, Atlas, Short Metadata OBU, Metadata Group OBU, Padding,
  // Temporal Delimiter, and Buffer removal timing.
  if (obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID &&
      !(obu_header.type == OBU_TEMPORAL_DELIMITER ||
        obu_header.type == OBU_METADATA_SHORT ||
        obu_header.type == OBU_METADATA_GROUP ||
        obu_header.type == OBU_BUFFER_REMOVAL_TIMING ||
        obu_header.type == OBU_LAYER_CONFIGURATION_RECORD ||
        obu_header.type == OBU_ATLAS_SEGMENT ||
        obu_header.type == OBU_OPERATING_POINT_SET ||
        obu_header.type == OBU_MULTI_STREAM_DECODER_OPERATION ||
        obu_header.type == OBU_PADDING)) {
    avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                       "Incorrect layer_id for %s: xlayer_id %d",
                       avm_obu_type_to_string(obu_header.type),
                       obu_header.obu_xlayer_id);
  }

  // CLK/OLK are only present in temporal layer 0
  if ((obu_header.type == OBU_CLOSED_LOOP_KEY ||
       obu_header.type == OBU_OPEN_LOOP_KEY) &&
      obu_header.obu_tlayer_id != 0) {
    avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                       "Incorrect tlayer_id for %s: tlayer_id %d",
                       avm_obu_type_to_string(obu_header.type),
                       obu_header.obu_tlayer_id);
  }
}

static BITSTREAM_PROFILE get_msdo_profile(struct AV2Decoder *pbi) {
  return pbi->common.msdo_params.multistream_profile_idc;
}

static BITSTREAM_PROFILE get_lcr_global_profile(struct AV2Decoder *pbi) {
  // Return the lcr_max_interop from the first valid global LCR's aggregate PTL.
  // lcr_max_interop maps directly to the IOP (0, 1, 2) which corresponds to
  // MAIN_420_10_IP0, MAIN_420_10_IP1, MAIN_420_10_IP2 respectively.
  for (int j = 0; j < MAX_NUM_LCR; j++) {
    if (pbi->lcr_list[GLOBAL_XLAYER_ID][j].valid) {
      const struct GlobalLayerConfigurationRecord *glcr =
          &pbi->lcr_list[GLOBAL_XLAYER_ID][j].global_lcr;
      if (glcr->lcr_aggregate_info_present_flag)
        return (BITSTREAM_PROFILE)glcr->aggregate_ptl.lcr_max_interop;
      // Fall back to the first seq PTL if aggregate PTL is not present.
      if (glcr->lcr_seq_profile_tier_level_info_present_flag &&
          glcr->LcrMaxNumXLayerCount > 0)
        return (BITSTREAM_PROFILE)glcr->seq_ptl[glcr->LcrXLayerID[0]]
            .lcr_seq_profile_idc;
    }
  }
  (void)pbi;
  return MAIN_420_10_IP2;  // Fallback
}

static BITSTREAM_PROFILE get_lcr_local_profile(struct AV2Decoder *pbi) {
  // Return the lcr_seq_profile_idc from the first valid local LCR.
  for (int i = 0; i < GLOBAL_XLAYER_ID; i++) {
    for (int j = 0; j < MAX_NUM_LCR; j++) {
      if (pbi->lcr_list[i][j].valid) {
        const struct LocalLayerConfigurationRecord *llcr =
            &pbi->lcr_list[i][j].local_lcr;
        if (llcr->lcr_profile_tier_level_info_present_flag)
          return (BITSTREAM_PROFILE)llcr->seq_ptl.lcr_seq_profile_idc;
      }
    }
  }
  (void)pbi;
  return MAIN_420_10_IP2;  // Fallback
}

// Conformance check for the presence of MSDO and LCR.
// The OBU requirements table in Annex A only applies when IOP < 3, i.e.
// when the MSDO profile or LCR profile is one of the IP profiles (0, 1, 2).
// For profile == 31 (CONFIGURABLE) the table does
// not impose additional constraints, so we return true.
bool conformance_check_msdo_lcr(struct AV2Decoder *pbi, bool global_lcr_present,
                                bool local_lcr_present) {
  int msdo_present = pbi->multistream_decoder_mode;
  int num_extended_layers = 0;
  int num_embedded_layers = 0;

  for (int i = 0; i < AVM_MAX_NUM_STREAMS - 1; i++) {
    if (pbi->xlayer_id_map[i] > 0) num_extended_layers++;
  }
  for (int i = 0; i < MAX_NUM_MLAYERS; i++) {
    bool found = false;
    for (int x = 0; x < AVM_MAX_NUM_STREAMS; x++) {
      if (pbi->mlayer_id_map[x][i] > 0) {
        found = true;
        break;
      }
    }
    if (found) num_embedded_layers++;
  }
  assert(num_extended_layers > 0 && num_embedded_layers > 0);

  // Determine the effective MSDO and LCR profiles.
  const BITSTREAM_PROFILE msdo_prof = get_msdo_profile(pbi);
  const BITSTREAM_PROFILE glcr_prof = get_lcr_global_profile(pbi);
  const BITSTREAM_PROFILE llcr_prof = get_lcr_local_profile(pbi);

  // The IOP table only applies for IOPs 0-2 (profiles IP0, IP1, IP2).
  // If the signaled profile is >= 3, the table does not apply.
  if (msdo_present && msdo_prof == CONFIGURABLE) return true;
  if (global_lcr_present && glcr_prof == CONFIGURABLE) return true;
  if (!msdo_present && !global_lcr_present && local_lcr_present &&
      llcr_prof == CONFIGURABLE) {
    return true;
  }
  if (!msdo_present && !global_lcr_present && !local_lcr_present) {
    return true;
  }

  if (num_extended_layers == 1 && num_embedded_layers == 1) {
    if (!msdo_present) return true;
  }

  if (num_extended_layers > 1 && num_embedded_layers == 1) {
    if (msdo_present &&
        (msdo_prof == MAIN_420_10_IP0 || msdo_prof == MAIN_420_10_IP1 ||
         msdo_prof == MAIN_420_10_IP2 || msdo_prof == MAIN_422_10_IP1 ||
         msdo_prof == MAIN_444_10_IP1))
      return true;

    if (global_lcr_present && glcr_prof == MAIN_420_10_IP2) return true;
  }

  if (num_extended_layers == 1 && num_embedded_layers > 1) {
    if (!msdo_present && local_lcr_present &&
        (llcr_prof == MAIN_420_10_IP1 || llcr_prof == MAIN_422_10_IP1 ||
         llcr_prof == MAIN_444_10_IP1))
      return true;

    if (!msdo_present && (global_lcr_present || local_lcr_present) &&
        (glcr_prof == MAIN_420_10_IP2 || llcr_prof == MAIN_420_10_IP2))
      return true;
  }

  if (num_extended_layers > 1 && num_embedded_layers > 1) {
    if (msdo_present && local_lcr_present && msdo_prof == MAIN_420_10_IP2)
      return true;

    if (global_lcr_present && glcr_prof == MAIN_420_10_IP2) return true;
  }
  return false;
}

static int get_ops_mlayer_count(const struct OpsMLayerInfo *mlayer_info,
                                int xlayer_id) {
  if (mlayer_info == NULL) return 0;
  if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS) return 0;
  return mlayer_info->OPMLayerCount[xlayer_id];
}

static int get_ops_tlayer_count(const struct OpsMLayerInfo *mlayer_info,
                                int xlayer_id, int mlayer_id) {
  if (mlayer_info == NULL) return 0;
  if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS) return 0;
  if (mlayer_id < 0 || mlayer_id >= MAX_NUM_MLAYERS) return 0;

  return mlayer_info->OPTLayerCount[xlayer_id][mlayer_id];
}

// Default operating point set id
static const int default_ops_id = 0;
// Default operating point index
static const int default_op_index = 0;

// Select the operating point for the current xlayer.
// If the user has explicitly set an OPS via codec control, use that;
// otherwise fall back to the default (ops_id=0, op_index=0).
// This function determines the OPS ID to use:
// 1. selected_ops_id if >= 0 (user-specified via AV2D_SET_SELECTED_OPS)
// 2. otherwise, default_ops_id (0)
//
// When sbe_state.retention_map_ready is set, num_mlayers and num_tlayers are
// derived directly from the SBE retention map so that the decoder layer counts
// are consistent with the OBU-level filtering already applied by Annex F.
// When the retention map is not ready (no OPS selected, SBE disabled, or map
// not yet built), the decoder decodes all layers in the bitstream.
static void avm_set_current_operating_point(struct AV2Decoder *pbi,
                                            int xlayer_id) {
  struct DecOperatingPointParams *dec_op_params = &pbi->dec_op_params;

  // If the Annex F retention map is ready, derive num_mlayers and num_tlayers
  // from it so that the decoder layer counts match the OBU filter exactly.
  // This ensures avm_set_current_operating_point and the SBE are always
  // consistent and share the same source of truth.
  if (pbi->sbe_state.retention_map_ready && pbi->sbe_state.extraction_enabled) {
    int xid = xlayer_id;
    if (xid < 0 || xid >= MAX_NUM_XLAYERS) {
      avm_internal_error(&pbi->common.error, AVM_CODEC_UNSUP_BITSTREAM,
                         "Invalid xlayer_id %d for operating point selection",
                         xid);
    }

    // Count distinct mlayers retained for this xlayer.
    unsigned int num_mlayers = 0;
    for (int j = 0; j < MAX_NUM_MLAYERS; j++) {
      for (int k = 0; k < MAX_NUM_TLAYERS; k++) {
        if (pbi->sbe_state.retention_map[xid][j][k]) {
          num_mlayers = j + 1;
          break;
        }
      }
    }

    // Count distinct tlayers retained for the base mlayer of this xlayer.
    unsigned int num_tlayers = 0;
    for (int k = 0; k < MAX_NUM_TLAYERS; k++) {
      for (int j = 0; j < MAX_NUM_MLAYERS; j++) {
        if (pbi->sbe_state.retention_map[xid][j][k]) {
          num_tlayers = k + 1;
          break;
        }
      }
    }

    dec_op_params->num_mlayers = num_mlayers > 0 ? num_mlayers : 1;
    dec_op_params->num_tlayers = num_tlayers > 0 ? num_tlayers : 1;
    dec_op_params->isValid = 1;
    // dec_ops/dec_op/DecOpSetId etc. are still populated below from the OPS
    // for use by other decoder subsystems that inspect those fields.
  }

  int target_ops_id =
      pbi->selected_ops_id >= 0 ? pbi->selected_ops_id : default_ops_id;

  // Only proceed to check for the operating point iff the ops id is valid
  if (target_ops_id < 0 || target_ops_id >= MAX_NUM_OPS_ID) return;

  // Determine the target op point index.
  int target_op_index =
      (pbi->selected_ops_id >= 0) ? pbi->selected_op_index : default_op_index;

  struct OperatingPointSet *ops = &pbi->ops_list[xlayer_id][target_ops_id];
  dec_op_params->dec_ops = ops;
  dec_op_params->dec_op = dec_op_params->dec_ops->op;
  if (!ops->valid || ops->ops_id != target_ops_id) {
    dec_op_params->DecOpSetId = 0;
    dec_op_params->DecOpCount = 0;
    dec_op_params->DecOpIndex = 0;
    // If SBE retention map is not ready or not enabled, decode all layers.
    if (!pbi->sbe_state.retention_map_ready ||
        !pbi->sbe_state.extraction_enabled) {
      dec_op_params->num_mlayers = MAX_NUM_MLAYERS;
      dec_op_params->num_tlayers = MAX_NUM_TLAYERS;
    }
    dec_op_params->isValid = 0;  // No matching OPS
    dec_op_params->dec_ops = NULL;
    dec_op_params->dec_op = NULL;
    // No OPS was available, all layers to be decoded.
    return;
  }

  // NOTE: if a GLOBAL vs. LOCAL OPS is to be used, then
  // Additional logic can be placed here to differentiate between
  // the global and local OPS
  if (target_op_index >= 0 && target_op_index < ops->ops_cnt) {
    struct OperatingPoint *op = &ops->op[target_op_index];
    dec_op_params->DecOpSetId = ops->ops_id;
    dec_op_params->DecOpCount = ops->ops_cnt;
    dec_op_params->DecOpIndex = target_op_index;
    dec_op_params->DecXlayerId = ops->obu_xlayer_id;
    int xlayer = dec_op_params->DecXlayerId;
    // Only overwrite num_mlayers/num_tlayers from OPS if the SBE retention
    // map has not already set them. The SBE map is the authoritative source
    // when Annex F extraction is active.
    if (!pbi->sbe_state.retention_map_ready ||
        !pbi->sbe_state.extraction_enabled) {
      dec_op_params->num_mlayers =
          get_ops_mlayer_count(&op->mlayer_info, xlayer);
      dec_op_params->num_tlayers =
          get_ops_tlayer_count(&op->mlayer_info, xlayer, pbi->common.mlayer_id);
      if (dec_op_params->num_mlayers == 0) dec_op_params->num_mlayers = 1;
      if (dec_op_params->num_tlayers == 0) dec_op_params->num_tlayers = 1;
    }
    dec_op_params->dec_op = op;
    dec_op_params->isValid = 1;
    return;
  }
  return;
}

// On success, sets *p_data_end and returns a boolean that indicates whether
// the decoding of the current frame is finished. On failure, sets
// cm->error.error_code and returns -1.
int avm_decode_frame_from_obus(struct AV2Decoder *pbi, const uint8_t *data,
                               const uint8_t *data_end,
                               const uint8_t **p_data_end) {
#if CONFIG_COLLECT_COMPONENT_TIMING
  start_timing(pbi, avm_decode_frame_from_obus_time);
#endif
  AV2_COMMON *const cm = &pbi->common;
  int frame_decoding_finished = 0;
  ObuHeader obu_header;
  memset(&obu_header, 0, sizeof(obu_header));

  // Enable is_multistream if multiple extended layers are present.
  // Enable multistream_decoder_mode only when an MSDO OBU is present.
  if (pbi->obus_in_frame_unit_data[0][0][OBU_MULTI_STREAM_DECODER_OPERATION]) {
    pbi->is_multistream = 1;
    pbi->multistream_decoder_mode = 1;
  } else if (pbi->glcr_is_present_in_tu) {
    pbi->is_multistream = 1;
  }

  // Check for CMVS end at a random access point TU without MSDO.
  // Per spec, the CMVS ends when a new CVS begins without an activated
  // global LCR.  prescan_glcr_will_activate is computed during OBU scanning
  // (check_frame_unit_data) by extracting seq_lcr_id from the frame header's
  // sequence header, then checking local LCRs first and falling back to
  // global LCRs (replicating find_active_lcr logic).
  if (pbi->is_multistream) {
    int tid = pbi->current_tlayer_id;
    int mid = pbi->current_mlayer_id;
    if (tid >= 0 && mid >= 0 &&
        pbi->obus_in_frame_unit_data[0][0][OBU_TEMPORAL_DELIMITER] &&
        !pbi->obus_in_frame_unit_data[0][0]
                                     [OBU_MULTI_STREAM_DECODER_OPERATION] &&
        (pbi->obus_in_frame_unit_data[tid][mid][OBU_CLOSED_LOOP_KEY] ||
         pbi->obus_in_frame_unit_data[tid][mid][OBU_OPEN_LOOP_KEY] ||
         pbi->obus_in_frame_unit_data[tid][mid][OBU_RAS_FRAME])) {
      if (!pbi->prescan_glcr_will_activate) {
        // Flush and reset like a config change
        if (pbi->stream_info != NULL) {
          flush_all_xlayer_frames(pbi, cm, true);
          avm_free(pbi->stream_info);
          pbi->stream_info = NULL;
          pbi->glcr_stream_info_num_allocated = 0;
        }
        cm->num_streams = 0;
        for (int i = 0; i < AVM_MAX_NUM_STREAMS; i++) pbi->xlayer_id_map[i] = 0;

        pbi->is_multistream = 0;
      }
      pbi->multistream_decoder_mode = 0;
    }
  }

  // Per-TU conformance check: validate the PREVIOUS TU's accumulated
  // xlayer/mlayer maps before resetting them for the current TU.
  // This must run once per TU (here), not per-OBU (inside the loop).
  if (pbi->random_accessed) {
    int num_xlayers = 0;
    int num_mlayers = 0;
    for (int i = 0; i < AVM_MAX_NUM_STREAMS - 1; i++) {
      if (pbi->xlayer_id_map[i] > 0) num_xlayers++;
    }
    for (int i = 0; i < MAX_NUM_MLAYERS; i++) {
      bool found = false;
      for (int x = 0; x < AVM_MAX_NUM_STREAMS; x++) {
        if (pbi->mlayer_id_map[x][i] > 0) {
          found = true;
          break;
        }
      }
      if (found) num_mlayers++;
    }

    if (num_xlayers > 0 && num_mlayers > 0) {
      bool global_lcr_present = false;
      bool local_lcr_present = false;
      for (int j = 0; j < MAX_NUM_LCR; j++) {
        if (pbi->lcr_list[GLOBAL_XLAYER_ID][j].valid) global_lcr_present = true;
      }
      for (int i = 0; i < GLOBAL_XLAYER_ID && !local_lcr_present; i++) {
        for (int j = 0; j < MAX_NUM_LCR; j++) {
          if (pbi->lcr_list[i][j].valid) {
            local_lcr_present = true;
            break;
          }
        }
      }

      if (!conformance_check_msdo_lcr(pbi, global_lcr_present,
                                      local_lcr_present)) {
        avm_internal_error(
            &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
            "An MSDO or LCR OBU in the current CVS violates the requirements "
            "of bitstream conformance for MSDO and LCR");
      }
    }

    // Reset maps for the current TU
    for (int i = 0; i < AVM_MAX_NUM_STREAMS - 1; i++) pbi->xlayer_id_map[i] = 0;
    for (int x = 0; x < AVM_MAX_NUM_STREAMS; x++)
      for (int i = 0; i < MAX_NUM_MLAYERS; i++) pbi->mlayer_id_map[x][i] = 0;
  }

  pbi->seen_frame_header = 0;
  pbi->next_start_tile = 0;
  pbi->num_tile_groups = 0;
  pbi->msdo_is_present_in_tu = 0;
  pbi->glcr_is_present_in_tu = 0;

  if (data_end < data) {
    cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
    return -1;
  }

  int count_obus_with_frame_unit = 0;
  obu_info *obu_list = pbi->obu_list;
  uint32_t acc_qm_id_bitmap = 0;
  int qm_bit_map_zero_signalled = 0;
  // acc_fgm_id_bitmap accumulates fgm_id_bitmap in FGM OBU to check if film
  // grain models signalled before a coded frame have the same fgm_id
  uint32_t acc_fgm_id_bitmap = 0;
  uint32_t acc_mfh_id_bitmap = 0;
  uint16_t acc_sh_id_bitmap[MAX_NUM_XLAYERS] = { 0 };
  uint8_t acc_lcr_id_bitmap[MAX_NUM_XLAYERS] = { 0 };
  int prev_obu_xlayer_id = -1;
  // prev_obu_type, prev_xlayer_id and tu_validation_state are used to compare
  // obus in this "data"
  OBU_TYPE prev_obu_type = NUM_OBU_TYPES;
  int prev_xlayer_id = -1;
  temporal_unit_state_t tu_validation_state = TU_STATE_START;

  // decode frame as a series of OBUs
  while (!frame_decoding_finished && cm->error.error_code == AVM_CODEC_OK) {
    struct avm_read_bit_buffer rb;
    size_t payload_size = 0;
    size_t decoded_payload_size = 0;
    size_t obu_payload_offset = 0;
    size_t bytes_read = 0;
    const size_t bytes_available = data_end - data;

    if (bytes_available == 0 && !pbi->seen_frame_header) {
      cm->error.error_code = AVM_CODEC_OK;
      break;
    }

    avm_codec_err_t status = avm_read_obu_header_and_size(
        data, bytes_available, &obu_header, &payload_size, &bytes_read);

    if (status != AVM_CODEC_OK) {
      cm->error.error_code = status;
      return -1;
    }

    // Note: avm_read_obu_header_and_size() takes care of checking that this
    // doesn't cause 'data' to advance past 'data_end'.
    data += bytes_read;

    if ((size_t)(data_end - data) < payload_size) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return -1;
    }

    // Annex F: Sub-bitstream extraction.
    // When extraction is enabled, trigger retention map construction when
    // transitioning from structural OBUs to non-structural OBUs, then
    // filter OBUs based on the retention map.
    if (pbi->sbe_state.extraction_enabled) {
      if (!pbi->sbe_state.retention_map_ready &&
          !is_sbe_structural_obu(obu_header.type)) {
        av2_sbe_build_retention_map(&pbi->sbe_state, pbi,
                                    obu_header.obu_xlayer_id);
      }
      if (pbi->sbe_state.retention_map_ready) {
        if (!av2_sbe_should_retain_obu(
                &pbi->sbe_state, obu_header.type, obu_header.obu_xlayer_id,
                obu_header.obu_mlayer_id, obu_header.obu_tlayer_id)) {
          pbi->sbe_state.obus_removed++;
          data += payload_size;
          continue;
        }
        pbi->sbe_state.obus_retained++;
      }
    }

    if (av2_is_leading_vcl_obu(obu_header.type))
      cm->is_leading_picture = 1;
    else if (av2_is_regular_vcl_obu(obu_header.type))
      cm->is_leading_picture = 0;
    else
      cm->is_leading_picture = -1;
    pbi->xlayer_id_map[obu_header.obu_xlayer_id] = 1;
    pbi->mlayer_id_map[obu_header.obu_xlayer_id][obu_header.obu_mlayer_id] = 1;

    obu_info *const curr_obu_info = &obu_list[count_obus_with_frame_unit];
    curr_obu_info->obu_type = obu_header.type;
    curr_obu_info->is_vcl = is_single_tile_vcl_obu(obu_header.type) ||
                            is_multi_tile_vcl_obu(obu_header.type);
    if (curr_obu_info->is_vcl) {
      assert(curr_obu_info->xlayer_id == obu_header.obu_xlayer_id);
      assert(curr_obu_info->mlayer_id == obu_header.obu_mlayer_id);
      assert(curr_obu_info->tlayer_id == obu_header.obu_tlayer_id);
    }
    curr_obu_info->first_tile_group = -1;
    curr_obu_info->immediate_output_picture = -1;
    curr_obu_info->showable_frame = -1;
    curr_obu_info->display_order_hint = -1;

    // Extract metadata_is_suffix for metadata OBUs
    // Per spec sections 5.17.2 and 5.17.3, metadata_is_suffix is f(1) - the
    // first bit of the payload
    int metadata_is_suffix = -1;  // -1 means not applicable
    if (obu_header.type == OBU_METADATA_SHORT ||
        obu_header.type == OBU_METADATA_GROUP) {
      if (payload_size == 0) {
        avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                           "OBU_METADATA_x has an empty payload");
      }
      // data has been advanced by bytes_read, so data[0] is first payload byte
      metadata_is_suffix = (data[0] & 0x80) >> 7;
    }

    // Validate OBU ordering within temporal units. Ignore padding OBUs and
    // reserved OBUs in this check.
    if (avm_obu_type_is_valid(obu_header.type) &&
        obu_header.type != OBU_PADDING) {
      if (!check_temporal_unit_structure(
              &tu_validation_state, obu_header.type, obu_header.obu_xlayer_id,
              metadata_is_suffix, prev_obu_type, prev_xlayer_id)) {
        avm_internal_error(
            &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
            "OBU order violation: current OBU %s with xlayer_id %d mlayer_id "
            "%d previous OBU %s(xlayer_id=%d) invalid in current state",
            avm_obu_type_to_string(obu_header.type), obu_header.obu_xlayer_id,
            obu_header.obu_mlayer_id, avm_obu_type_to_string(prev_obu_type),
            prev_xlayer_id);
      }
      prev_obu_type = obu_header.type;
      prev_xlayer_id = obu_header.obu_xlayer_id;
    }

    check_valid_layer_id(obu_header, cm);

    pbi->obu_type = obu_header.type;

    cm->tlayer_id = obu_header.obu_tlayer_id;
    cm->mlayer_id = obu_header.obu_mlayer_id;

    if (!pbi->is_multistream || (obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID &&
                                 cm->xlayer_id == GLOBAL_XLAYER_ID)) {
      cm->xlayer_id = obu_header.obu_xlayer_id;
    } else if (cm->xlayer_id != GLOBAL_XLAYER_ID &&
               obu_header.obu_xlayer_id == GLOBAL_XLAYER_ID) {
      // Store xlayer context
      av2_store_xlayer_context(pbi, cm, cm->xlayer_id);
      cm->xlayer_id = obu_header.obu_xlayer_id;
    } else if (cm->xlayer_id == GLOBAL_XLAYER_ID &&
               obu_header.obu_xlayer_id != GLOBAL_XLAYER_ID) {
      // Restore xlayer context
      cm->xlayer_id = obu_header.obu_xlayer_id;
      av2_restore_xlayer_context(pbi, cm, cm->xlayer_id);
    } else if (cm->xlayer_id != obu_header.obu_xlayer_id) {
      // Store and restore xlayer context
      av2_store_xlayer_context(pbi, cm, cm->xlayer_id);
      cm->xlayer_id = obu_header.obu_xlayer_id;
      av2_restore_xlayer_context(pbi, cm, cm->xlayer_id);
    }

    if (obu_header.type == OBU_LEADING_TILE_GROUP ||
        obu_header.type == OBU_REGULAR_TILE_GROUP) {
      if (prev_obu_xlayer_id == -1) {
        prev_obu_xlayer_id = obu_header.obu_xlayer_id;
      } else {
        if (pbi->is_multistream && prev_obu_xlayer_id >= 0 &&
            obu_header.obu_xlayer_id != prev_obu_xlayer_id) {
          avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                             "tile group OBUs with the same stream_id shall "
                             "be contiguous within a temporal unit");
        }
      }
    }
    // Set is_bridge_frame flag based on OBU type
    if (obu_header.type == OBU_BRIDGE_FRAME) {
      cm->bridge_frame_info.is_bridge_frame = 1;
    } else {
      cm->bridge_frame_info.is_bridge_frame = 0;
    }

    if (is_single_tile_vcl_obu(obu_header.type) ||
        is_multi_tile_vcl_obu(obu_header.type)) {
      if (!pbi->seen_vcl_obu_in_this_tu) {
        pbi->this_is_first_vcl_obu_in_tu = 1;
        pbi->seen_vcl_obu_in_this_tu = 1;
      } else {
        pbi->this_is_first_vcl_obu_in_tu = 0;
      }
    }

    // Flush remaining frames after xlayer context is correctly set.
    // This must happen after xlayer switching but before processing frame OBUs.
    // Skip flush while in GLOBAL_XLAYER_ID context -- the target layer's
    // context has not been restored yet; flush will run once a non-global
    // OBU restores it.
    if (pbi->this_is_first_vcl_obu_in_tu && cm->xlayer_id != GLOBAL_XLAYER_ID &&
        obu_header.type == OBU_CLOSED_LOOP_KEY &&
        pbi->obus_in_frame_unit_data[cm->tlayer_id][cm->mlayer_id]
                                    [OBU_CLOSED_LOOP_KEY]) {
      flush_remaining_frames(pbi, INT_MAX);
      // Reset last_output_doh for this xlayer only, after flushing so that
      // the flushed frames' DOH values are still checked against the old
      // last_output_doh.  Other xlayers may still be mid-sequence.
      for (int ml = 0; ml < MAX_NUM_MLAYERS; ml++)
        pbi->last_output_doh[cm->xlayer_id][ml] = -1;
    }

    // Flush leading frames (doh < last_olk_tu_display_order_hint) at the start
    // of the first regular temporal unit after an OLK, before
    // reset_buffer_other_than_OLK() clears their DPB slots.
    if (pbi->olk_encountered && pbi->this_is_first_vcl_obu_in_tu &&
        !pbi->seen_frame_header && cm->is_leading_picture == 0) {
      flush_remaining_frames(pbi, pbi->last_olk_tu_display_order_hint);
    }

    av2_init_read_bit_buffer(pbi, &rb, data, data + payload_size);
    switch (obu_header.type) {
      case OBU_TEMPORAL_DELIMITER:
        // Reset per-TU state unconditionally for the current stream.
        decoded_payload_size = read_temporal_delimiter_obu();
        pbi->seen_frame_header = 0;
        pbi->next_start_tile = 0;
        pbi->seen_vcl_obu_in_this_tu = 0;
        pbi->doh_tu_order_hint_bits_set = 0;
        for (int i = 0; i < NUM_CUSTOM_QMS; i++) pbi->qm_protected[i] = 0;

        // Propagate the reset to each active xlayer's saved context.
        for (int xlayer = 0; xlayer < AVM_MAX_NUM_STREAMS - 1; xlayer++) {
          if (pbi->xlayer_id_map[xlayer] > 0) {
            av2_store_xlayer_context(pbi, cm, cm->xlayer_id);
            cm->xlayer_id = xlayer;
            av2_restore_xlayer_context(pbi, cm, xlayer);
            decoded_payload_size = read_temporal_delimiter_obu();
            pbi->seen_frame_header = 0;
            pbi->next_start_tile = 0;
            pbi->seen_vcl_obu_in_this_tu = 0;
            pbi->doh_tu_order_hint_bits_set = 0;
            for (int i = 0; i < NUM_CUSTOM_QMS; i++) pbi->qm_protected[i] = 0;
          }
        }
        break;
      case OBU_MULTI_STREAM_DECODER_OPERATION:
        decoded_payload_size =
            read_multi_stream_decoder_operation_obu(pbi, &rb);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        if (pbi->sbe_state.extraction_enabled) {
          av2_sbe_process_msdo(&pbi->sbe_state, cm->num_streams,
                               cm->stream_ids);
        }
        break;
      case OBU_SEQUENCE_HEADER:
        cm->xlayer_id = obu_header.obu_xlayer_id;
        decoded_payload_size = read_sequence_header_obu(
            pbi, obu_header.obu_xlayer_id, &rb, acc_sh_id_bitmap);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        if (pbi->sbe_state.extraction_enabled) {
          av2_sbe_extract_seq_header_params(
              &pbi->sbe_state, obu_header.obu_xlayer_id,
              cm->seq_params.seq_profile_idc, cm->seq_params.seq_max_level_idx,
              cm->seq_params.seq_tier, cm->seq_params.seq_max_mlayer_cnt);
        }
        break;
      case OBU_BUFFER_REMOVAL_TIMING:
        decoded_payload_size =
            av2_read_buffer_removal_timing_obu(pbi, &rb, cm->xlayer_id);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_LAYER_CONFIGURATION_RECORD:
        decoded_payload_size = av2_read_layer_configuration_record_obu(
            pbi, cm->xlayer_id, &rb, acc_lcr_id_bitmap);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        // Allocate or reallocate stream_info if Global LCR triggers
        // multi-stream mode and more space is needed.
        if (cm->xlayer_id == GLOBAL_XLAYER_ID && pbi->glcr_is_present_in_tu &&
            !pbi->msdo_is_present_in_tu &&
            pbi->glcr_num_xlayers > pbi->glcr_stream_info_num_allocated) {
          const int num_streams = pbi->glcr_num_xlayers;
          const int old_count = pbi->glcr_stream_info_num_allocated;
          StreamInfo *new_info =
              (StreamInfo *)avm_malloc(num_streams * sizeof(StreamInfo));
          if (new_info == NULL) {
            avm_internal_error(&cm->error, AVM_CODEC_MEM_ERROR,
                               "Memory allocation failed for pbi->stream_info "
                               "(GLCR)\n");
          }
          memset(new_info, 0, num_streams * sizeof(StreamInfo));
          if (pbi->stream_info != NULL && old_count > 0) {
            memcpy(new_info, pbi->stream_info, old_count * sizeof(StreamInfo));
            avm_free(pbi->stream_info);
          }
          for (int i = old_count; i < num_streams; i++) {
            init_stream_info(&new_info[i]);
          }
          pbi->stream_info = new_info;
          pbi->glcr_stream_info_num_allocated = num_streams;
        }
        if (pbi->sbe_state.extraction_enabled &&
            cm->xlayer_id == GLOBAL_XLAYER_ID && pbi->glcr_is_present_in_tu) {
          av2_sbe_process_global_lcr(&pbi->sbe_state, cm->num_streams,
                                     cm->stream_ids);
        }
        break;
      case OBU_ATLAS_SEGMENT:
        decoded_payload_size =
            av2_read_atlas_segment_info_obu(pbi, cm->xlayer_id, &rb);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_OPERATING_POINT_SET:
        decoded_payload_size =
            av2_read_operating_point_set_obu(pbi, cm->xlayer_id, &rb);
        avm_set_current_operating_point(pbi, cm->xlayer_id);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        if (pbi->sbe_state.extraction_enabled) {
          if (cm->xlayer_id == GLOBAL_XLAYER_ID) {
            // Check if the user-selected global OPS is now available
            if (pbi->selected_ops_id >= 0 &&
                pbi->selected_ops_id < MAX_NUM_OPS_ID) {
              const struct OperatingPointSet *sel_ops =
                  &pbi->ops_list[GLOBAL_XLAYER_ID][pbi->selected_ops_id];
              if (sel_ops->valid) {
                av2_sbe_process_global_ops(
                    &pbi->sbe_state, sel_ops->ops_id, sel_ops->ops_cnt,
                    pbi->selected_ops_id, pbi->selected_op_index,
                    (sel_ops->ops_cnt > 0 && pbi->selected_op_index >= 0 &&
                     pbi->selected_op_index < sel_ops->ops_cnt)
                        ? sel_ops->op[pbi->selected_op_index].ops_xlayer_map
                        : 0,
                    sel_ops->ops_mlayer_info_idc);
              }
            }
          } else {
            av2_sbe_process_local_ops(&pbi->sbe_state, pbi, cm->xlayer_id,
                                      pbi->dec_op_params.DecOpSetId,
                                      pbi->dec_op_params.DecOpCount);
          }
        }
        break;
      case OBU_CONTENT_INTERPRETATION:
        decoded_payload_size = av2_read_content_interpretation_obu(pbi, &rb);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_MULTI_FRAME_HEADER:
        decoded_payload_size =
            read_multi_frame_header_obu(pbi, &acc_mfh_id_bitmap, &rb);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_CLOSED_LOOP_KEY:
      case OBU_OPEN_LOOP_KEY:
      case OBU_LEADING_TILE_GROUP:
      case OBU_REGULAR_TILE_GROUP:
      case OBU_SWITCH:
      case OBU_LEADING_SEF:
      case OBU_REGULAR_SEF:
      case OBU_LEADING_TIP:
      case OBU_REGULAR_TIP:
      case OBU_RAS_FRAME:
      case OBU_BRIDGE_FRAME:
        // Constraint 4: Switch frames shall be at the base temporal layer.
        if ((obu_header.type == OBU_SWITCH ||
             obu_header.type == OBU_RAS_FRAME) &&
            obu_header.obu_tlayer_id != 0) {
          avm_internal_error(
              &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
              "Switch frame (obu_type=%d) must have obu_tlayer_id equal to "
              "0, got %d.",
              obu_header.type, obu_header.obu_tlayer_id);
        }
        if (obu_header.type == OBU_SWITCH || obu_header.type == OBU_RAS_FRAME) {
          const int mid = obu_header.obu_mlayer_id;
          for (int j = 0; j < MAX_NUM_MLAYERS; j++) {
            if (j != mid && cm->seq_params.mlayer_dependency_map[mid][j] != 0) {
              avm_internal_error(
                  &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                  "Switch frame at mlayer_id=%d depends on mlayer %d. "
                  "Switch frames must be in independent embedded layers.",
                  mid, j);
            }
          }
        }
        // Drop picture unit HLS state that was derived exclusively from leading
        // frame picture units when the first regular VCL OBU is encountered.
        if (av2_is_leading_vcl_obu(obu_header.type)) {
          // Tag every MFH/QM/FGM/CI/BRT signalled in the leading temporal
          // unit so we can identify and discard them at the transition.
          const int leading_mlayer_id = cm->mlayer_id;
          for (int i = 0; i < MAX_MFH_NUM; i++)
            if (acc_mfh_id_bitmap & (1 << i))
              cm->mfh_params[i].mfh_from_leading = 1;
          for (int i = 0; i < NUM_CUSTOM_QMS; i++)
            if ((acc_qm_id_bitmap & (1 << i)) || qm_bit_map_zero_signalled) {
              pbi->qm_list[i].qm_from_leading = 1;
            }
          for (int i = 0; i < MAX_FGM_NUM; i++)
            if (acc_fgm_id_bitmap & (1 << i)) {
              pbi->fgm_list[i].fgm_from_leading = 1;
            }
          if (pbi->obus_in_frame_unit_data[obu_header.obu_tlayer_id]
                                          [leading_mlayer_id]
                                          [OBU_CONTENT_INTERPRETATION])
            cm->ci_params_per_layer[leading_mlayer_id].ci_from_leading = true;
          if (pbi->obus_in_frame_unit_data[obu_header.obu_tlayer_id]
                                          [leading_mlayer_id]
                                          [OBU_BUFFER_REMOVAL_TIMING])
            cm->brt_info.brt_from_leading = true;
          for (int x = 0; x < MAX_NUM_XLAYERS; x++)
            for (int s = 0; s < MAX_SEQ_NUM; s++)
              if (acc_sh_id_bitmap[x] & (1 << s))
                pbi->seq_list[x][s].sh_from_leading = 1;
          for (int x = 0; x < MAX_NUM_XLAYERS; x++)
            for (int l = 0; l < MAX_NUM_LCR; l++)
              if (acc_lcr_id_bitmap[x] & (1 << l))
                pbi->lcr_list[x][l].lcr_from_leading = true;
        } else if (pbi->this_is_first_vcl_obu_in_tu == 1) {
          // SEF, TIP, SWITCH, RAS, BRIDGE, TG (not CLK)
          // MFH, QM, FGM, BRT, CI signalled in leading temporal unit cannot
          // be used. Drop state not re-signalled in the regular picture unit.
          const int regular_mlayer_id = cm->mlayer_id;
          const int num_planes = av2_num_planes(cm);

          for (int i = 0; i < NUM_CUSTOM_QMS; i++) {
            struct quantization_matrix_set *qmset = &pbi->qm_list[i];
            if (qmset->qm_from_leading == 1 && !(acc_qm_id_bitmap & (1 << i)) &&
                !qm_bit_map_zero_signalled) {
              qmset->qm_id = -1;
              qmset->qm_mlayer_id = -1;
              qmset->qm_tlayer_id = -1;
              qmset->quantizer_matrix_num_planes = num_planes;
              qmset->is_user_defined_qm = false;
              pbi->qm_protected[i] = 0;
            }
            qmset->qm_from_leading = 0;
          }

          for (int i = 0; i < MAX_FGM_NUM; i++) {
            if (pbi->fgm_list[i].fgm_from_leading == 1 &&
                !(acc_fgm_id_bitmap & (1 << i))) {
              pbi->fgm_list[i].fgm_id = -1;
              pbi->fgm_list[i].fgm_tlayer_id = -1;
              pbi->fgm_list[i].fgm_mlayer_id = -1;
            }
            pbi->fgm_list[i].fgm_from_leading = 0;
          }

          if (cm->ci_params_per_layer[regular_mlayer_id].ci_from_leading == 1 &&
              !pbi->obus_in_frame_unit_data[obu_header.obu_tlayer_id]
                                           [regular_mlayer_id]
                                           [OBU_CONTENT_INTERPRETATION]) {
            av2_initialize_ci_params(
                &cm->ci_params_per_layer[regular_mlayer_id]);
          }
          cm->ci_params_per_layer[regular_mlayer_id].ci_from_leading = false;
          for (int i = 0; i < MAX_MFH_NUM; i++) {
            if (cm->mfh_params[i].mfh_from_leading == 1 &&
                !(acc_mfh_id_bitmap & (1 << i))) {
              cm->mfh_valid[i] = false;
            }
            cm->mfh_params[i].mfh_from_leading = 0;
          }
          if (cm->brt_info.brt_from_leading == 1 &&
              !pbi->obus_in_frame_unit_data[obu_header.obu_tlayer_id]
                                           [regular_mlayer_id]
                                           [OBU_BUFFER_REMOVAL_TIMING]) {
            memset(&cm->brt_info, 0, sizeof(cm->brt_info));
          }
          cm->brt_info.brt_from_leading = false;
          for (int x = 0; x < MAX_NUM_XLAYERS; x++) {
            for (int s = 0; s < MAX_SEQ_NUM; s++) {
              if (!(pbi->seq_list[x][s].sh_from_leading == 1 &&
                    !(acc_sh_id_bitmap[x] & (1 << s))))
                pbi->seq_list[x][s].sh_from_leading = 0;
            }
            for (int l = 0; l < MAX_NUM_LCR; l++) {
              if (!(pbi->lcr_list[x][l].lcr_from_leading &&
                    !(acc_lcr_id_bitmap[x] & (1 << l))))
                pbi->lcr_list[x][l].lcr_from_leading = false;
            }
          }
        }

        // It is a requirement that if multiple QM OBUs are present
        // consecutively prior to a coded frame, other than a QM OBU with
        // qm_bit_map equal to 0, such QM OBUs will not set the same QM ID more
        // than once.
        acc_qm_id_bitmap = 0;
        qm_bit_map_zero_signalled = 0;
        // It is a requirement that if multiple FGM OBUs are present
        // consecutively prior to a coded frame, such FGM OBUs will not set
        // the same FGM ID more than once.
        acc_fgm_id_bitmap = 0;
        acc_mfh_id_bitmap = 0;
        memset(acc_sh_id_bitmap, 0, sizeof(acc_sh_id_bitmap));
        memset(acc_lcr_id_bitmap, 0, sizeof(acc_lcr_id_bitmap));
        decoded_payload_size = read_tilegroup_obu(
            pbi, &rb, data, data + payload_size, p_data_end, obu_header.type,
            obu_header.obu_xlayer_id, &curr_obu_info->first_tile_group,
            &frame_decoding_finished);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        curr_obu_info->immediate_output_picture = cm->immediate_output_picture;
        curr_obu_info->showable_frame =
            cm->immediate_output_picture || cm->implicit_output_picture;
        curr_obu_info->display_order_hint =
            cm->current_frame.display_order_hint;
        int num_mlayers = 0;
        for (int i = 0; i < MAX_NUM_MLAYERS; ++i) {
          if (pbi->mlayer_id_map[obu_header.obu_xlayer_id][i] == 1)
            num_mlayers++;
        }
        if (num_mlayers > 1 &&
            num_mlayers > cm->seq_params.seq_max_mlayer_cnt) {
          avm_internal_error(
              &cm->error, AVM_CODEC_UNSUP_BITSTREAM,
              "The number of embedded layers (%d) in the current video "
              "sequence is larger than the seq_max_mlayer_cnt (%d).",
              num_mlayers, cm->seq_params.seq_max_mlayer_cnt);
        }
        if (cm->bru.frame_inactive_flag ||
            cm->bridge_frame_info.is_bridge_frame) {
          pbi->seen_frame_header = 0;
          frame_decoding_finished = 1;
          CommonTileParams *const tiles = &cm->tiles;
          av2_get_tile_limits(
              &cm->tiles, cm->mi_params.mi_rows, cm->mi_params.mi_cols,
              cm->mib_size_log2, cm->seq_params.mib_size_log2,
              cm->seq_params.seq_max_level_idx, cm->seq_params.seq_tier);
          tiles->uniform_spacing = 1;
          tiles->log2_cols = 0;
          av2_calculate_tile_cols(tiles);
          tiles->log2_rows = 0;
          av2_calculate_tile_rows(tiles);
          const int num_tiles = cm->tiles.cols * cm->tiles.rows;
          const int end_tile = num_tiles - 1;
          // skip parsing and go directly to decode
          av2_decode_tg_tiles_and_wrapup(pbi, data, data + payload_size,
                                         p_data_end, 0, end_tile, 0);
          if (cm->bridge_frame_info.is_bridge_frame) {
            *p_data_end = data + payload_size;
          }
          break;
        }
        if (obu_payload_offset > payload_size) {
          cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
          return -1;
        }
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        if (frame_decoding_finished) pbi->seen_frame_header = 0;
        pbi->num_tile_groups++;
        break;
      case OBU_QUANTIZATION_MATRIX:
        decoded_payload_size =
            read_qm_obu(pbi, obu_header.obu_tlayer_id, obu_header.obu_mlayer_id,
                        &acc_qm_id_bitmap, &qm_bit_map_zero_signalled, &rb);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_METADATA_SHORT:
        decoded_payload_size = read_metadata_short(pbi, data, payload_size, 0);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_METADATA_GROUP:
        decoded_payload_size =
            read_metadata_obu(pbi, data, payload_size, &obu_header, 0);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_FILM_GRAIN_MODEL:
        decoded_payload_size =
            read_fgm_obu(pbi, obu_header.obu_tlayer_id,
                         obu_header.obu_mlayer_id, &acc_fgm_id_bitmap, &rb);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      case OBU_PADDING:
        decoded_payload_size = read_padding(cm, data, payload_size);
        if (cm->error.error_code != AVM_CODEC_OK) return -1;
        break;
      default:
        // Skip unrecognized OBUs
        if (payload_size > 0 &&
            get_last_nonzero_byte(data, payload_size) == 0) {
          cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
          return -1;
        }
        decoded_payload_size = payload_size;
        break;
    }

    // Spec 6.2.2:
    // "At the end of reading the OBU, it is a requirement of bitstream "
    // "conformance that obu_mlayer_id is less than or equal to max_mlayer_id."
    if (obu_header.obu_mlayer_id > cm->seq_params.max_mlayer_id &&
        (!pbi->decoding_first_frame || frame_decoding_finished)) {
      avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                         "obu_mlayer_id (%d) must be less than or equal to "
                         "max_mlayer_id (%d).",
                         obu_header.obu_mlayer_id,
                         cm->seq_params.max_mlayer_id);
    }

    // Check that the signalled OBU size matches the actual amount of data read
    if (decoded_payload_size > payload_size) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return -1;
    }

    // If there are extra padding bytes, they should all be zero
    while (decoded_payload_size < payload_size) {
      uint8_t padding_byte = data[decoded_payload_size++];
      if (padding_byte != 0) {
        cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
        return -1;
      }
    }

    data += payload_size;
    count_obus_with_frame_unit++;
  }

  // check whether suffix metadata OBUs are present
  while (cm->error.error_code == AVM_CODEC_OK && data < data_end) {
    size_t payload_size = 0;
    size_t decoded_payload_size = 0;
    size_t bytes_read = 0;
    const size_t bytes_available = data_end - data;
    avm_codec_err_t status = avm_read_obu_header_and_size(
        data, bytes_available, &obu_header, &payload_size, &bytes_read);

    if (status != AVM_CODEC_OK) {
      cm->error.error_code = status;
      return -1;
    }

    // Accept both OBU_METADATA_SHORT and OBU_METADATA_GROUP for suffix metadata
    if (!(is_metadata_obu(obu_header.type) || (obu_header.type == OBU_PADDING)))
      break;

    // Note: avm_read_obu_header_and_size() takes care of checking that this
    // doesn't cause 'data' to advance past 'data_end'.
    data += bytes_read;

    if ((size_t)(data_end - data) < payload_size) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return -1;
    }

    if (obu_header.type == OBU_PADDING) {
      decoded_payload_size = read_padding(cm, data, payload_size);
      obu_info *const curr_obu_info = &obu_list[count_obus_with_frame_unit];
      curr_obu_info->obu_type = obu_header.type;
      curr_obu_info->first_tile_group = -1;
      curr_obu_info->immediate_output_picture = -1;
      curr_obu_info->showable_frame = -1;
      curr_obu_info->display_order_hint = -1;
      count_obus_with_frame_unit++;
      if (cm->error.error_code != AVM_CODEC_OK) return -1;
    } else if (is_metadata_obu(obu_header.type)) {
      // check whether it is a suffix metadata OBU
      if (payload_size == 0) {
        avm_internal_error(&cm->error, AVM_CODEC_CORRUPT_FRAME,
                           "OBU_METADATA_x has an empty payload");
      }
      if (!(data[0] & 0x80)) {
        avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                           "OBU order violation: OBU_METADATA_x(prefix) cannot "
                           "be present after a coded frame");
      }

      // Call the appropriate read function based on OBU type
      if (obu_header.type == OBU_METADATA_GROUP) {
        decoded_payload_size =
            read_metadata_obu(pbi, data, payload_size, &obu_header, 1);
      } else {
        decoded_payload_size = read_metadata_short(pbi, data, payload_size, 1);
      }
      obu_info *const curr_obu_info = &obu_list[count_obus_with_frame_unit];
      curr_obu_info->obu_type = obu_header.type;
      curr_obu_info->first_tile_group = -1;
      curr_obu_info->immediate_output_picture = -1;
      curr_obu_info->showable_frame = -1;
      curr_obu_info->display_order_hint = -1;
      count_obus_with_frame_unit++;
    }
    if (cm->error.error_code != AVM_CODEC_OK) return -1;

    // Spec 6.2.2:
    // "At the end of reading the OBU, it is a requirement of bitstream "
    // "conformance that obu_mlayer_id is less than or equal to max_mlayer_id."
    if (obu_header.obu_mlayer_id > cm->seq_params.max_mlayer_id &&
        (!pbi->decoding_first_frame || frame_decoding_finished)) {
      avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                         "obu_mlayer_id (%d) must be less than or equal to "
                         "max_mlayer_id (%d).",
                         obu_header.obu_mlayer_id,
                         cm->seq_params.max_mlayer_id);
    }

    // Check that the signalled OBU size matches the actual amount of data read
    if (decoded_payload_size > payload_size) {
      cm->error.error_code = AVM_CODEC_CORRUPT_FRAME;
      return -1;
    }

    data += payload_size;
  }

  if (cm->error.error_code != AVM_CODEC_OK) return -1;

  *p_data_end = data;

#if CONFIG_COLLECT_COMPONENT_TIMING
  end_timing(pbi, avm_decode_frame_from_obus_time);

  // Print out timing information.
  int i;
  fprintf(stderr, "\n Frame number: %d, Frame type: %s, Show Frame: %d\n",
          cm->current_frame.frame_number,
          get_frame_type_enum(cm->current_frame.frame_type),
          cm->immediate_output_picture);
  for (i = 0; i < kTimingComponents; i++) {
    pbi->component_time[i] += pbi->frame_component_time[i];
    fprintf(stderr, " %s:  %" PRId64 " us (total: %" PRId64 " us)\n",
            get_component_name(i), pbi->frame_component_time[i],
            pbi->component_time[i]);
    pbi->frame_component_time[i] = 0;
  }
#endif

  obu_info current_frame_unit;
  memset(&current_frame_unit, -1, sizeof(current_frame_unit));
  for (int obu_idx = 0; obu_idx < count_obus_with_frame_unit; obu_idx++) {
    obu_info *this_obu = &obu_list[obu_idx];

    if (this_obu->first_tile_group == 1) {
      current_frame_unit = *this_obu;
      pbi->num_displayable_frame_unit[this_obu->mlayer_id]++;
    }
    if (is_multi_tile_vcl_obu(this_obu->obu_type) &&
        this_obu->first_tile_group == 0) {
      // Allow padding OBUs between tile group OBUs.
      int prev_obu_idx = obu_idx - 1;
      while (prev_obu_idx >= 0 &&
             obu_list[prev_obu_idx].obu_type == OBU_PADDING) {
        prev_obu_idx--;
      }
      if (prev_obu_idx < 0) {
        avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM,
                           "The first non-padding OBU in a frame unit cannot "
                           "be a tile group with is_first_tile_group == 0");
      }
      check_tilegroup_obus_in_a_frame_unit(cm, this_obu,
                                           &obu_list[prev_obu_idx]);
    }
  }

  // When SBE filters out all frame OBUs in a temporal unit, no frame was
  // found: skip the consistency checks.
  int xId = current_frame_unit.xlayer_id;
  if (current_frame_unit.display_order_hint != -1 &&
      pbi->last_frame_unit[xId].display_order_hint != -1 &&
      (pbi->last_frame_unit[xId].xlayer_id == current_frame_unit.xlayer_id)) {
    check_clk_in_a_layer(cm, &current_frame_unit, &pbi->last_frame_unit[xId]);

    if (current_frame_unit.showable_frame == 0) {
      check_layerid_hidden_frame_units(cm, &current_frame_unit,
                                       &pbi->last_frame_unit[xId]);
    } else {
      check_layerid_showable_frame_units(
          cm, &current_frame_unit, &pbi->last_frame_unit[xId],
          &pbi->last_displayable_frame_unit[xId]);
    }
  }

  return frame_decoding_finished;
}
