/* GStreamer
 * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * NOTE: some of implementations are copied/modified from Chromium code
 *
 * Copyright 2015 The Chromium Authors. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *    * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * SECTION:gsth264decoder
 * @title: GstH264Decoder
 * @short_description: Base class to implement stateless H.264 decoders
 * @sources:
 * - gsth264picture.h
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gst/base/base.h>
#include "gsth264decoder.h"
#include "gsth264picture-private.h"

GST_DEBUG_CATEGORY (gst_h264_decoder_debug);
#define GST_CAT_DEFAULT gst_h264_decoder_debug

typedef enum
{
  GST_H264_DECODER_FORMAT_NONE,
  GST_H264_DECODER_FORMAT_AVC,
  GST_H264_DECODER_FORMAT_BYTE
} GstH264DecoderFormat;

typedef enum
{
  GST_H264_DECODER_ALIGN_NONE,
  GST_H264_DECODER_ALIGN_NAL,
  GST_H264_DECODER_ALIGN_AU
} GstH264DecoderAlign;

struct _GstH264DecoderPrivate
{
  GstH264DecoderCompliance compliance;

  guint8 profile_idc;
  gint width, height;

  guint nal_length_size;

  /* state */
  GstH264DecoderFormat in_format;
  GstH264DecoderAlign align;
  GstH264NalParser *parser;
  GstH264Dpb *dpb;
  /* Cache last field which can not enter the DPB, should be a non ref */
  GstH264Picture *last_field;

  /* used for low-latency vs. high throughput mode decision */
  gboolean is_live;

  /* sps/pps of the current slice */
  const GstH264SPS *active_sps;
  const GstH264PPS *active_pps;

  /* Picture currently being processed/decoded */
  GstH264Picture *current_picture;
  GstVideoCodecFrame *current_frame;

  /* Slice (slice header + nalu) currently being processed/decodec */
  GstH264Slice current_slice;

  gint max_frame_num;
  gint max_pic_num;
  gint max_long_term_frame_idx;

  gint prev_frame_num;
  gint prev_ref_frame_num;
  gint prev_frame_num_offset;
  gboolean prev_has_memmgmnt5;

  /* Values related to previously decoded reference picture */
  gboolean prev_ref_has_memmgmnt5;
  gint prev_ref_top_field_order_cnt;
  gint prev_ref_pic_order_cnt_msb;
  gint prev_ref_pic_order_cnt_lsb;

  GstH264PictureField prev_ref_field;

  gboolean process_ref_pic_lists;
  guint preferred_output_delay;

  /* Reference picture lists, constructed for each frame */
  GArray *ref_pic_list_p0;
  GArray *ref_pic_list_b0;
  GArray *ref_pic_list_b1;

  /* Temporary picture list, for reference picture lists in fields,
   * corresponding to 8.2.4.2.2 refFrameList0ShortTerm, refFrameList0LongTerm
   * and 8.2.4.2.5 refFrameList1ShortTerm and refFrameListLongTerm */
  GArray *ref_frame_list_0_short_term;
  GArray *ref_frame_list_1_short_term;
  GArray *ref_frame_list_long_term;

  /* Reference picture lists, constructed for each slice */
  GArray *ref_pic_list0;
  GArray *ref_pic_list1;

  /* Split packetized data into actual nal chunks (for malformed stream) */
  GArray *split_nalu;

  /* For delayed output */
  GstQueueArray *output_queue;

  gboolean input_state_changed;

  /* Return value from output_picture() */
  GstFlowReturn last_flow;

  /* Latency report params */
  guint32 max_reorder_count;
  guint32 last_reorder_frame_number;
  gint fps_n;
  gint fps_d;
};

typedef struct
{
  /* Holds ref */
  GstVideoCodecFrame *frame;
  GstH264Picture *picture;
  /* Without ref */
  GstH264Decoder *self;
} GstH264DecoderOutputFrame;

#define UPDATE_FLOW_RETURN(ret,new_ret) G_STMT_START { \
  if (*(ret) == GST_FLOW_OK) \
    *(ret) = new_ret; \
} G_STMT_END

#define parent_class gst_h264_decoder_parent_class
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstH264Decoder, gst_h264_decoder,
    GST_TYPE_VIDEO_DECODER,
    G_ADD_PRIVATE (GstH264Decoder);
    GST_DEBUG_CATEGORY_INIT (gst_h264_decoder_debug, "h264decoder", 0,
        "H.264 Video Decoder"));

static void gst_h264_decoder_finalize (GObject * object);

static gboolean gst_h264_decoder_start (GstVideoDecoder * decoder);
static gboolean gst_h264_decoder_stop (GstVideoDecoder * decoder);
static gboolean gst_h264_decoder_set_format (GstVideoDecoder * decoder,
    GstVideoCodecState * state);
static gboolean gst_h264_decoder_negotiate (GstVideoDecoder * decoder);
static GstFlowReturn gst_h264_decoder_finish (GstVideoDecoder * decoder);
static gboolean gst_h264_decoder_flush (GstVideoDecoder * decoder);
static GstFlowReturn gst_h264_decoder_drain (GstVideoDecoder * decoder);
static GstFlowReturn gst_h264_decoder_handle_frame (GstVideoDecoder * decoder,
    GstVideoCodecFrame * frame);

/* codec specific functions */
static GstFlowReturn gst_h264_decoder_process_sps (GstH264Decoder * self,
    GstH264SPS * sps);
static GstFlowReturn gst_h264_decoder_decode_slice (GstH264Decoder * self);
static GstFlowReturn gst_h264_decoder_decode_nal (GstH264Decoder * self,
    GstH264NalUnit * nalu);
static gboolean gst_h264_decoder_fill_picture_from_slice (GstH264Decoder * self,
    const GstH264Slice * slice, GstH264Picture * picture);
static gboolean gst_h264_decoder_calculate_poc (GstH264Decoder * self,
    GstH264Picture * picture);
static gboolean gst_h264_decoder_init_gap_picture (GstH264Decoder * self,
    GstH264Picture * picture, gint frame_num);
static GstFlowReturn gst_h264_decoder_drain_internal (GstH264Decoder * self);
static void gst_h264_decoder_finish_current_picture (GstH264Decoder * self,
    GstFlowReturn * ret);
static void gst_h264_decoder_finish_picture (GstH264Decoder * self,
    GstH264Picture * picture, GstFlowReturn * ret);
static void gst_h264_decoder_prepare_ref_pic_lists (GstH264Decoder * self,
    GstH264Picture * current_picture);
static void gst_h264_decoder_clear_ref_pic_lists (GstH264Decoder * self);
static gboolean gst_h264_decoder_modify_ref_pic_lists (GstH264Decoder * self);
static gboolean
gst_h264_decoder_sliding_window_picture_marking (GstH264Decoder * self,
    GstH264Picture * picture);
static void gst_h264_decoder_do_output_picture (GstH264Decoder * self,
    GstH264Picture * picture, GstFlowReturn * ret);
static GstH264Picture *gst_h264_decoder_new_field_picture (GstH264Decoder *
    self, GstH264Picture * picture);
static void
gst_h264_decoder_clear_output_frame (GstH264DecoderOutputFrame * output_frame);

enum
{
  PROP_0,
  PROP_COMPLIANCE,
};

/**
 * gst_h264_decoder_compliance_get_type:
 *
 * Get the compliance type of the h264 decoder.
 *
 * Since: 1.20
 */
GType
gst_h264_decoder_compliance_get_type (void)
{
  static gsize h264_decoder_compliance_type = 0;
  static const GEnumValue compliances[] = {
    {GST_H264_DECODER_COMPLIANCE_AUTO, "GST_H264_DECODER_COMPLIANCE_AUTO",
        "auto"},
    {GST_H264_DECODER_COMPLIANCE_STRICT, "GST_H264_DECODER_COMPLIANCE_STRICT",
        "strict"},
    {GST_H264_DECODER_COMPLIANCE_NORMAL, "GST_H264_DECODER_COMPLIANCE_NORMAL",
        "normal"},
    {GST_H264_DECODER_COMPLIANCE_FLEXIBLE,
        "GST_H264_DECODER_COMPLIANCE_FLEXIBLE", "flexible"},
    {0, NULL, NULL},
  };


  if (g_once_init_enter (&h264_decoder_compliance_type)) {
    GType _type;

    _type = g_enum_register_static ("GstH264DecoderCompliance", compliances);
    g_once_init_leave (&h264_decoder_compliance_type, _type);
  }

  return (GType) h264_decoder_compliance_type;
}

static void
gst_h264_decoder_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  GstH264Decoder *self = GST_H264_DECODER (object);
  GstH264DecoderPrivate *priv = self->priv;

  switch (property_id) {
    case PROP_COMPLIANCE:
      GST_OBJECT_LOCK (self);
      g_value_set_enum (value, priv->compliance);
      GST_OBJECT_UNLOCK (self);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
gst_h264_decoder_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  GstH264Decoder *self = GST_H264_DECODER (object);
  GstH264DecoderPrivate *priv = self->priv;

  switch (property_id) {
    case PROP_COMPLIANCE:
      GST_OBJECT_LOCK (self);
      priv->compliance = g_value_get_enum (value);
      GST_OBJECT_UNLOCK (self);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
gst_h264_decoder_class_init (GstH264DecoderClass * klass)
{
  GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = GST_DEBUG_FUNCPTR (gst_h264_decoder_finalize);
  object_class->get_property = gst_h264_decoder_get_property;
  object_class->set_property = gst_h264_decoder_set_property;

  decoder_class->start = GST_DEBUG_FUNCPTR (gst_h264_decoder_start);
  decoder_class->stop = GST_DEBUG_FUNCPTR (gst_h264_decoder_stop);
  decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_h264_decoder_set_format);
  decoder_class->negotiate = GST_DEBUG_FUNCPTR (gst_h264_decoder_negotiate);
  decoder_class->finish = GST_DEBUG_FUNCPTR (gst_h264_decoder_finish);
  decoder_class->flush = GST_DEBUG_FUNCPTR (gst_h264_decoder_flush);
  decoder_class->drain = GST_DEBUG_FUNCPTR (gst_h264_decoder_drain);
  decoder_class->handle_frame =
      GST_DEBUG_FUNCPTR (gst_h264_decoder_handle_frame);

  /**
   * GstH264Decoder:compliance:
   *
   * The compliance controls the behavior of the decoder to handle some
   * subtle cases and contexts, such as the low-latency DPB bumping or
   * mapping the baseline profile as the constrained-baseline profile,
   * etc.
   *
   * Since: 1.20
   */
  g_object_class_install_property (object_class, PROP_COMPLIANCE,
      g_param_spec_enum ("compliance", "Decoder Compliance",
          "The decoder's behavior in compliance with the h264 spec.",
          GST_TYPE_H264_DECODER_COMPLIANCE, GST_H264_DECODER_COMPLIANCE_AUTO,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT));
}

static void
gst_h264_decoder_init (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv;

  gst_video_decoder_set_packetized (GST_VIDEO_DECODER (self), TRUE);
  gst_video_decoder_set_needs_format (GST_VIDEO_DECODER (self), TRUE);

  self->priv = priv = gst_h264_decoder_get_instance_private (self);

  priv->ref_pic_list_p0 = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);
  g_array_set_clear_func (priv->ref_pic_list_p0,
      (GDestroyNotify) gst_clear_h264_picture);

  priv->ref_pic_list_b0 = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);
  g_array_set_clear_func (priv->ref_pic_list_b0,
      (GDestroyNotify) gst_clear_h264_picture);

  priv->ref_pic_list_b1 = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);
  g_array_set_clear_func (priv->ref_pic_list_b1,
      (GDestroyNotify) gst_clear_h264_picture);

  priv->ref_frame_list_0_short_term = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);
  g_array_set_clear_func (priv->ref_frame_list_0_short_term,
      (GDestroyNotify) gst_clear_h264_picture);

  priv->ref_frame_list_1_short_term = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);
  g_array_set_clear_func (priv->ref_frame_list_1_short_term,
      (GDestroyNotify) gst_clear_h264_picture);

  priv->ref_frame_list_long_term = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);
  g_array_set_clear_func (priv->ref_frame_list_long_term,
      (GDestroyNotify) gst_clear_h264_picture);

  priv->ref_pic_list0 = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);
  priv->ref_pic_list1 = g_array_sized_new (FALSE, TRUE,
      sizeof (GstH264Picture *), 32);

  priv->split_nalu = g_array_new (FALSE, FALSE, sizeof (GstH264NalUnit));

  priv->output_queue =
      gst_queue_array_new_for_struct (sizeof (GstH264DecoderOutputFrame), 1);
  gst_queue_array_set_clear_func (priv->output_queue,
      (GDestroyNotify) gst_h264_decoder_clear_output_frame);
}

static void
gst_h264_decoder_finalize (GObject * object)
{
  GstH264Decoder *self = GST_H264_DECODER (object);
  GstH264DecoderPrivate *priv = self->priv;

  g_array_unref (priv->ref_pic_list_p0);
  g_array_unref (priv->ref_pic_list_b0);
  g_array_unref (priv->ref_pic_list_b1);
  g_array_unref (priv->ref_frame_list_0_short_term);
  g_array_unref (priv->ref_frame_list_1_short_term);
  g_array_unref (priv->ref_frame_list_long_term);
  g_array_unref (priv->ref_pic_list0);
  g_array_unref (priv->ref_pic_list1);
  g_array_unref (priv->split_nalu);
  gst_queue_array_free (priv->output_queue);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_h264_decoder_reset_latency_infos (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv = self->priv;

  priv->max_reorder_count = 0;
  priv->last_reorder_frame_number = 0;
  priv->fps_n = 25;
  priv->fps_d = 1;
}

static void
gst_h264_decoder_reset (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv = self->priv;

  g_clear_pointer (&self->input_state, gst_video_codec_state_unref);
  g_clear_pointer (&priv->parser, gst_h264_nal_parser_free);
  g_clear_pointer (&priv->dpb, gst_h264_dpb_free);
  gst_clear_h264_picture (&priv->last_field);

  priv->profile_idc = 0;
  priv->width = 0;
  priv->height = 0;
  priv->nal_length_size = 4;
  priv->last_flow = GST_FLOW_OK;

  gst_h264_decoder_reset_latency_infos (self);
}

static gboolean
gst_h264_decoder_start (GstVideoDecoder * decoder)
{
  GstH264Decoder *self = GST_H264_DECODER (decoder);
  GstH264DecoderPrivate *priv = self->priv;

  gst_h264_decoder_reset (self);

  priv->parser = gst_h264_nal_parser_new ();
  priv->dpb = gst_h264_dpb_new ();

  return TRUE;
}

static gboolean
gst_h264_decoder_stop (GstVideoDecoder * decoder)
{
  GstH264Decoder *self = GST_H264_DECODER (decoder);

  gst_h264_decoder_reset (self);

  return TRUE;
}

static void
gst_h264_decoder_clear_output_frame (GstH264DecoderOutputFrame * output_frame)
{
  if (!output_frame)
    return;

  if (output_frame->frame) {
    gst_video_decoder_release_frame (GST_VIDEO_DECODER (output_frame->self),
        output_frame->frame);
    output_frame->frame = NULL;
  }

  gst_clear_h264_picture (&output_frame->picture);
}

static void
gst_h264_decoder_clear_dpb (GstH264Decoder * self, gboolean flush)
{
  GstVideoDecoder *decoder = GST_VIDEO_DECODER (self);
  GstH264DecoderPrivate *priv = self->priv;
  GstH264Picture *picture;

  /* If we are not flushing now, videodecoder baseclass will hold
   * GstVideoCodecFrame. Release frames manually */
  if (!flush) {
    while ((picture = gst_h264_dpb_bump (priv->dpb, TRUE)) != NULL) {
      GstVideoCodecFrame *frame = gst_video_decoder_get_frame (decoder,
          GST_CODEC_PICTURE_FRAME_NUMBER (picture));

      if (frame)
        gst_video_decoder_release_frame (decoder, frame);
      gst_h264_picture_unref (picture);
    }
  }

  gst_queue_array_clear (priv->output_queue);
  gst_h264_decoder_clear_ref_pic_lists (self);
  gst_clear_h264_picture (&priv->last_field);
  gst_h264_dpb_clear (priv->dpb);
}

static gboolean
gst_h264_decoder_flush (GstVideoDecoder * decoder)
{
  GstH264Decoder *self = GST_H264_DECODER (decoder);

  gst_h264_decoder_clear_dpb (self, TRUE);

  return TRUE;
}

static GstFlowReturn
gst_h264_decoder_drain (GstVideoDecoder * decoder)
{
  GstH264Decoder *self = GST_H264_DECODER (decoder);

  /* dpb will be cleared by this method */
  return gst_h264_decoder_drain_internal (self);
}

static GstFlowReturn
gst_h264_decoder_finish (GstVideoDecoder * decoder)
{
  return gst_h264_decoder_drain (decoder);
}

static GstFlowReturn
gst_h264_decoder_handle_frame (GstVideoDecoder * decoder,
    GstVideoCodecFrame * frame)
{
  GstH264Decoder *self = GST_H264_DECODER (decoder);
  GstH264DecoderPrivate *priv = self->priv;
  GstBuffer *in_buf = frame->input_buffer;
  GstH264NalUnit nalu;
  GstH264ParserResult pres = GST_H264_PARSER_OK;
  GstMapInfo map;
  GstFlowReturn decode_ret = GST_FLOW_OK;

  GST_LOG_OBJECT (self,
      "handle frame, PTS: %" GST_TIME_FORMAT ", DTS: %"
      GST_TIME_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (in_buf)),
      GST_TIME_ARGS (GST_BUFFER_DTS (in_buf)));

  priv->current_frame = frame;
  priv->last_flow = GST_FLOW_OK;

  gst_buffer_map (in_buf, &map, GST_MAP_READ);
  if (priv->in_format == GST_H264_DECODER_FORMAT_AVC) {
    guint offset = 0;
    gsize consumed = 0;
    guint i;

    do {
      pres = gst_h264_parser_identify_and_split_nalu_avc (priv->parser,
          map.data, offset, map.size, priv->nal_length_size, priv->split_nalu,
          &consumed);
      if (pres != GST_H264_PARSER_OK)
        break;

      for (i = 0; i < priv->split_nalu->len; i++) {
        GstH264NalUnit *nl =
            &g_array_index (priv->split_nalu, GstH264NalUnit, i);
        decode_ret = gst_h264_decoder_decode_nal (self, nl);
        if (decode_ret != GST_FLOW_OK)
          break;
      }

      offset += consumed;
    } while (pres == GST_H264_PARSER_OK && decode_ret == GST_FLOW_OK);
  } else {
    pres = gst_h264_parser_identify_nalu (priv->parser,
        map.data, 0, map.size, &nalu);

    if (pres == GST_H264_PARSER_NO_NAL_END)
      pres = GST_H264_PARSER_OK;

    while (pres == GST_H264_PARSER_OK && decode_ret == GST_FLOW_OK) {
      decode_ret = gst_h264_decoder_decode_nal (self, &nalu);

      pres = gst_h264_parser_identify_nalu (priv->parser,
          map.data, nalu.offset + nalu.size, map.size, &nalu);

      if (pres == GST_H264_PARSER_NO_NAL_END)
        pres = GST_H264_PARSER_OK;
    }
  }

  gst_buffer_unmap (in_buf, &map);

  if (decode_ret != GST_FLOW_OK) {
    if (decode_ret == GST_FLOW_ERROR) {
      GST_VIDEO_DECODER_ERROR (self, 1, STREAM, DECODE,
          ("Failed to decode data"), (NULL), decode_ret);
    }

    gst_video_decoder_release_frame (decoder, frame);
    gst_clear_h264_picture (&priv->current_picture);
    priv->current_frame = NULL;

    return decode_ret;
  }

  gst_h264_decoder_finish_current_picture (self, &decode_ret);
  gst_video_codec_frame_unref (frame);
  priv->current_frame = NULL;

  if (priv->last_flow != GST_FLOW_OK) {
    GST_DEBUG_OBJECT (self,
        "Last flow %s", gst_flow_get_name (priv->last_flow));
    return priv->last_flow;
  }

  if (decode_ret == GST_FLOW_ERROR) {
    GST_VIDEO_DECODER_ERROR (self, 1, STREAM, DECODE,
        ("Failed to decode data"), (NULL), decode_ret);
  }

  return decode_ret;
}

static GstFlowReturn
gst_h264_decoder_parse_sps (GstH264Decoder * self, GstH264NalUnit * nalu)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264SPS sps;
  GstH264ParserResult pres;
  GstFlowReturn ret;

  pres = gst_h264_parse_sps (nalu, &sps);
  if (pres != GST_H264_PARSER_OK) {
    GST_WARNING_OBJECT (self, "Failed to parse SPS, result %d", pres);
    return GST_FLOW_ERROR;
  }

  GST_LOG_OBJECT (self, "SPS parsed");

  ret = gst_h264_decoder_process_sps (self, &sps);
  if (ret != GST_FLOW_OK) {
    GST_WARNING_OBJECT (self, "Failed to process SPS");
  } else if (gst_h264_parser_update_sps (priv->parser,
          &sps) != GST_H264_PARSER_OK) {
    GST_WARNING_OBJECT (self, "Failed to update SPS");
    ret = GST_FLOW_ERROR;
  }

  gst_h264_sps_clear (&sps);

  return ret;
}

static GstFlowReturn
gst_h264_decoder_parse_pps (GstH264Decoder * self, GstH264NalUnit * nalu)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264PPS pps;
  GstH264ParserResult pres;
  GstFlowReturn ret = GST_FLOW_OK;

  pres = gst_h264_parse_pps (priv->parser, nalu, &pps);
  if (pres != GST_H264_PARSER_OK) {
    GST_WARNING_OBJECT (self, "Failed to parse PPS, result %d", pres);
    return GST_FLOW_ERROR;
  }

  GST_LOG_OBJECT (self, "PPS parsed");

  if (pps.num_slice_groups_minus1 > 0) {
    GST_FIXME_OBJECT (self, "FMO is not supported");
    ret = GST_FLOW_ERROR;
  } else if (gst_h264_parser_update_pps (priv->parser, &pps)
      != GST_H264_PARSER_OK) {
    GST_WARNING_OBJECT (self, "Failed to update PPS");
    ret = GST_FLOW_ERROR;
  }

  gst_h264_pps_clear (&pps);

  return ret;
}

static GstFlowReturn
gst_h264_decoder_parse_codec_data (GstH264Decoder * self, const guint8 * data,
    gsize size)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264DecoderConfigRecord *config = NULL;
  GstFlowReturn ret = GST_FLOW_OK;
  GstH264NalUnit *nalu;
  guint i;

  if (gst_h264_parser_parse_decoder_config_record (priv->parser, data, size,
          &config) != GST_H264_PARSER_OK) {
    GST_WARNING_OBJECT (self, "Failed to parse codec-data");
    return GST_FLOW_ERROR;
  }

  priv->nal_length_size = config->length_size_minus_one + 1;
  for (i = 0; i < config->sps->len; i++) {
    nalu = &g_array_index (config->sps, GstH264NalUnit, i);

    /* TODO: handle subset sps for SVC/MVC. That would need to be stored in
     * separate array instead of putting SPS/subset-SPS into a single array */
    if (nalu->type != GST_H264_NAL_SPS)
      continue;

    ret = gst_h264_decoder_parse_sps (self, nalu);
    if (ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT (self, "Failed to parse SPS");
      goto out;
    }
  }

  for (i = 0; i < config->pps->len; i++) {
    nalu = &g_array_index (config->pps, GstH264NalUnit, i);
    if (nalu->type != GST_H264_NAL_PPS)
      continue;

    ret = gst_h264_decoder_parse_pps (self, nalu);
    if (ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT (self, "Failed to parse PPS");
      goto out;
    }
  }

out:
  gst_h264_decoder_config_record_free (config);
  return ret;
}

static gboolean
gst_h264_decoder_preprocess_slice (GstH264Decoder * self, GstH264Slice * slice)
{
  GstH264DecoderPrivate *priv = self->priv;

  if (!priv->current_picture) {
    if (slice->header.first_mb_in_slice != 0) {
      GST_ERROR_OBJECT (self, "Invalid stream, first_mb_in_slice %d",
          slice->header.first_mb_in_slice);
      return FALSE;
    }
  }

  return TRUE;
}

static void
gst_h264_decoder_update_pic_nums (GstH264Decoder * self,
    GstH264Picture * current_picture, gint frame_num)
{
  GstH264DecoderPrivate *priv = self->priv;
  GArray *dpb = gst_h264_dpb_get_pictures_all (priv->dpb);
  gint i;

  for (i = 0; i < dpb->len; i++) {
    GstH264Picture *picture = g_array_index (dpb, GstH264Picture *, i);

    if (!GST_H264_PICTURE_IS_REF (picture))
      continue;

    if (GST_H264_PICTURE_IS_LONG_TERM_REF (picture)) {
      if (GST_H264_PICTURE_IS_FRAME (current_picture))
        picture->long_term_pic_num = picture->long_term_frame_idx;
      else if (current_picture->field == picture->field)
        picture->long_term_pic_num = 2 * picture->long_term_frame_idx + 1;
      else
        picture->long_term_pic_num = 2 * picture->long_term_frame_idx;
    } else {
      if (picture->frame_num > frame_num)
        picture->frame_num_wrap = picture->frame_num - priv->max_frame_num;
      else
        picture->frame_num_wrap = picture->frame_num;

      if (GST_H264_PICTURE_IS_FRAME (current_picture))
        picture->pic_num = picture->frame_num_wrap;
      else if (picture->field == current_picture->field)
        picture->pic_num = 2 * picture->frame_num_wrap + 1;
      else
        picture->pic_num = 2 * picture->frame_num_wrap;
    }
  }

  g_array_unref (dpb);
}

static GstH264Picture *
gst_h264_decoder_split_frame (GstH264Decoder * self, GstH264Picture * picture)
{
  GstH264Picture *other_field;

  g_assert (GST_H264_PICTURE_IS_FRAME (picture));

  other_field = gst_h264_decoder_new_field_picture (self, picture);
  if (!other_field) {
    GST_WARNING_OBJECT (self,
        "Couldn't split frame into complementary field pair");
    return NULL;
  }

  GST_LOG_OBJECT (self, "Split picture %p, poc %d, frame num %d",
      picture, picture->pic_order_cnt, picture->frame_num);

  /* FIXME: enhance TFF decision by using picture timing SEI */
  if (picture->top_field_order_cnt < picture->bottom_field_order_cnt) {
    picture->field = GST_H264_PICTURE_FIELD_TOP_FIELD;
    picture->pic_order_cnt = picture->top_field_order_cnt;

    other_field->field = GST_H264_PICTURE_FIELD_BOTTOM_FIELD;
    other_field->pic_order_cnt = picture->bottom_field_order_cnt;
  } else {
    picture->field = GST_H264_PICTURE_FIELD_BOTTOM_FIELD;
    picture->pic_order_cnt = picture->bottom_field_order_cnt;

    other_field->field = GST_H264_PICTURE_FIELD_TOP_FIELD;
    other_field->pic_order_cnt = picture->top_field_order_cnt;
  }

  other_field->top_field_order_cnt = picture->top_field_order_cnt;
  other_field->bottom_field_order_cnt = picture->bottom_field_order_cnt;
  other_field->frame_num = picture->frame_num;
  other_field->ref = picture->ref;
  other_field->nonexisting = picture->nonexisting;
  GST_CODEC_PICTURE_COPY_FRAME_NUMBER (other_field, picture);
  other_field->field_pic_flag = picture->field_pic_flag;

  return other_field;
}

static void
output_picture_directly (GstH264Decoder * self, GstH264Picture * picture,
    GstFlowReturn * ret)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264Picture *out_pic = NULL;
  GstFlowReturn flow_ret = GST_FLOW_OK;

  g_assert (ret != NULL);

  if (GST_H264_PICTURE_IS_FRAME (picture)) {
    g_assert (priv->last_field == NULL);
    out_pic = g_steal_pointer (&picture);
    goto output;
  }

  if (priv->last_field == NULL) {
    if (picture->second_field) {
      GST_WARNING ("Set the last output %p poc:%d, without first field",
          picture, picture->pic_order_cnt);

      flow_ret = GST_FLOW_ERROR;
      goto output;
    }

    /* Just cache the first field. */
    priv->last_field = g_steal_pointer (&picture);
  } else {
    if (!picture->second_field || !picture->other_field
        || picture->other_field != priv->last_field) {
      GST_WARNING ("The last field %p poc:%d is not the pair of the "
          "current field %p poc:%d",
          priv->last_field, priv->last_field->pic_order_cnt,
          picture, picture->pic_order_cnt);

      gst_clear_h264_picture (&priv->last_field);
      flow_ret = GST_FLOW_ERROR;
      goto output;
    }

    GST_TRACE ("Pair the last field %p poc:%d and the current"
        " field %p poc:%d",
        priv->last_field, priv->last_field->pic_order_cnt,
        picture, picture->pic_order_cnt);

    out_pic = priv->last_field;
    priv->last_field = NULL;
    /* Link each field. */
    out_pic->other_field = picture;
  }

output:
  if (out_pic) {
    gst_h264_dpb_set_last_output (priv->dpb, out_pic);
    gst_h264_decoder_do_output_picture (self, out_pic, &flow_ret);
  }

  gst_clear_h264_picture (&picture);

  UPDATE_FLOW_RETURN (ret, flow_ret);
}

static void
add_picture_to_dpb (GstH264Decoder * self, GstH264Picture * picture)
{
  GstH264DecoderPrivate *priv = self->priv;

  if (!gst_h264_dpb_get_interlaced (priv->dpb)) {
    g_assert (priv->last_field == NULL);
    gst_h264_dpb_add (priv->dpb, picture);
    return;
  }

  /* The first field of the last picture may not be able to enter the
     DPB if it is a non ref, but if the second field enters the DPB, we
     need to add both of them. */
  if (priv->last_field && picture->other_field == priv->last_field) {
    gst_h264_dpb_add (priv->dpb, priv->last_field);
    priv->last_field = NULL;
  }

  gst_h264_dpb_add (priv->dpb, picture);
}

static void
_bump_dpb (GstH264Decoder * self, GstH264DpbBumpMode bump_level,
    GstH264Picture * current_picture, GstFlowReturn * ret)
{
  GstH264DecoderPrivate *priv = self->priv;

  g_assert (ret != NULL);

  while (gst_h264_dpb_needs_bump (priv->dpb, current_picture, bump_level)) {
    GstH264Picture *to_output;

    to_output = gst_h264_dpb_bump (priv->dpb, FALSE);

    if (!to_output) {
      GST_WARNING_OBJECT (self, "Bumping is needed but no picture to output");
      break;
    }

    gst_h264_decoder_do_output_picture (self, to_output, ret);
  }
}

static GstFlowReturn
gst_h264_decoder_handle_frame_num_gap (GstH264Decoder * self, gint frame_num)
{
  GstH264DecoderPrivate *priv = self->priv;
  const GstH264SPS *sps = priv->active_sps;
  gint unused_short_term_frame_num;

  if (!sps) {
    GST_ERROR_OBJECT (self, "No active sps");
    return GST_FLOW_ERROR;
  }

  if (priv->prev_ref_frame_num == frame_num) {
    GST_TRACE_OBJECT (self,
        "frame_num == PrevRefFrameNum (%d), not a gap", frame_num);
    return GST_FLOW_OK;
  }

  if (((priv->prev_ref_frame_num + 1) % priv->max_frame_num) == frame_num) {
    GST_TRACE_OBJECT (self,
        "frame_num ==  (PrevRefFrameNum + 1) %% MaxFrameNum (%d), not a gap",
        frame_num);
    return GST_FLOW_OK;
  }

  if (gst_h264_dpb_get_size (priv->dpb) == 0) {
    GST_TRACE_OBJECT (self, "DPB is empty, not a gap");
    return GST_FLOW_OK;
  }

  if (!sps->gaps_in_frame_num_value_allowed_flag) {
    /* This is likely the case where some frames were dropped.
     * then we need to keep decoding without error out */
    GST_WARNING_OBJECT (self, "Invalid frame num %d, maybe frame drop",
        frame_num);

    return GST_FLOW_OK;
  }

  GST_DEBUG_OBJECT (self, "Handling frame num gap %d -> %d (MaxFrameNum: %d)",
      priv->prev_ref_frame_num, frame_num, priv->max_frame_num);

  /* 7.4.3/7-23 */
  unused_short_term_frame_num =
      (priv->prev_ref_frame_num + 1) % priv->max_frame_num;
  while (unused_short_term_frame_num != frame_num) {
    GstH264Picture *picture = gst_h264_picture_new ();
    GstFlowReturn ret = GST_FLOW_OK;

    if (!gst_h264_decoder_init_gap_picture (self, picture,
            unused_short_term_frame_num))
      return GST_FLOW_ERROR;

    gst_h264_decoder_update_pic_nums (self, picture,
        unused_short_term_frame_num);

    /* C.2.1 */
    if (!gst_h264_decoder_sliding_window_picture_marking (self, picture)) {
      GST_ERROR_OBJECT (self,
          "Couldn't perform sliding window picture marking");
      return GST_FLOW_ERROR;
    }

    gst_h264_dpb_delete_unused (priv->dpb);

    _bump_dpb (self, GST_H264_DPB_BUMP_NORMAL_LATENCY, picture, &ret);
    if (ret != GST_FLOW_OK)
      return ret;

    /* the picture is short term ref, add to DPB. */
    if (gst_h264_dpb_get_interlaced (priv->dpb)) {
      GstH264Picture *other_field =
          gst_h264_decoder_split_frame (self, picture);

      add_picture_to_dpb (self, picture);
      add_picture_to_dpb (self, other_field);
    } else {
      add_picture_to_dpb (self, picture);
    }

    unused_short_term_frame_num++;
    unused_short_term_frame_num %= priv->max_frame_num;
  }

  return GST_FLOW_OK;
}

static gboolean
gst_h264_decoder_init_current_picture (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv = self->priv;

  if (!gst_h264_decoder_fill_picture_from_slice (self, &priv->current_slice,
          priv->current_picture)) {
    return FALSE;
  }

  if (!gst_h264_decoder_calculate_poc (self, priv->current_picture))
    return FALSE;

  /* If the slice header indicates we will have to perform reference marking
   * process after this picture is decoded, store required data for that
   * purpose */
  if (priv->current_slice.header.
      dec_ref_pic_marking.adaptive_ref_pic_marking_mode_flag) {
    priv->current_picture->dec_ref_pic_marking =
        priv->current_slice.header.dec_ref_pic_marking;
  }

  return TRUE;
}

static GstFlowReturn
gst_h264_decoder_start_current_picture (GstH264Decoder * self)
{
  GstH264DecoderClass *klass;
  GstH264DecoderPrivate *priv = self->priv;
  const GstH264SPS *sps;
  gint frame_num;
  GstFlowReturn ret = GST_FLOW_OK;
  GstH264Picture *current_picture;

  g_assert (priv->current_picture != NULL);
  g_assert (priv->active_sps != NULL);
  g_assert (priv->active_pps != NULL);

  current_picture = priv->current_picture;

  /* If subclass didn't update output state at this point,
   * marking this picture as a discont and stores current input state */
  if (priv->input_state_changed) {
    gst_h264_picture_set_discont_state (current_picture, self->input_state);
    priv->input_state_changed = FALSE;
  }

  sps = priv->active_sps;

  priv->max_frame_num = sps->max_frame_num;
  frame_num = priv->current_slice.header.frame_num;
  if (priv->current_slice.nalu.idr_pic_flag)
    priv->prev_ref_frame_num = 0;

  ret = gst_h264_decoder_handle_frame_num_gap (self, frame_num);
  if (ret != GST_FLOW_OK)
    return ret;

  if (!gst_h264_decoder_init_current_picture (self))
    return GST_FLOW_ERROR;

  /* If the new picture is an IDR, flush DPB */
  if (current_picture->idr) {
    if (!current_picture->dec_ref_pic_marking.no_output_of_prior_pics_flag) {
      ret = gst_h264_decoder_drain_internal (self);
      if (ret != GST_FLOW_OK)
        return ret;
    } else {
      /* C.4.4 Removal of pictures from the DPB before possible insertion
       * of the current picture
       *
       * If decoded picture is IDR and no_output_of_prior_pics_flag is equal to 1
       * or is inferred to be equal to 1, all frame buffers in the DPB
       * are emptied without output of the pictures they contain,
       * and DPB fullness is set to 0.
       */
      gst_h264_decoder_clear_dpb (self, FALSE);
    }
  }

  gst_h264_decoder_update_pic_nums (self, current_picture, frame_num);

  if (priv->process_ref_pic_lists)
    gst_h264_decoder_prepare_ref_pic_lists (self, current_picture);

  klass = GST_H264_DECODER_GET_CLASS (self);
  if (klass->start_picture) {
    ret = klass->start_picture (self, priv->current_picture,
        &priv->current_slice, priv->dpb);

    if (ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT (self, "subclass does not want to start picture");
      return ret;
    }
  }

  return GST_FLOW_OK;
}

static GstH264Picture *
gst_h264_decoder_new_field_picture (GstH264Decoder * self,
    GstH264Picture * picture)
{
  GstH264DecoderClass *klass = GST_H264_DECODER_GET_CLASS (self);
  GstH264Picture *new_picture;

  if (!klass->new_field_picture) {
    GST_WARNING_OBJECT (self, "Subclass does not support interlaced stream");
    return NULL;
  }

  new_picture = gst_h264_picture_new ();
  /* don't confuse subclass by non-existing picture */
  if (!picture->nonexisting) {
    GstFlowReturn ret;

    ret = klass->new_field_picture (self, picture, new_picture);
    if (ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT (self, "Subclass couldn't handle new field picture");
      gst_h264_picture_unref (new_picture);

      return NULL;
    }
  }

  new_picture->other_field = picture;
  new_picture->second_field = TRUE;

  return new_picture;
}

static gboolean
gst_h264_decoder_find_first_field_picture (GstH264Decoder * self,
    GstH264Slice * slice, GstH264Picture ** first_field)
{
  GstH264DecoderPrivate *priv = self->priv;
  const GstH264SliceHdr *slice_hdr = &slice->header;
  GstH264Picture *prev_field;
  gboolean in_dpb;

  *first_field = NULL;
  prev_field = NULL;
  in_dpb = FALSE;
  if (gst_h264_dpb_get_interlaced (priv->dpb)) {
    if (priv->last_field) {
      prev_field = priv->last_field;
      in_dpb = FALSE;
    } else if (gst_h264_dpb_get_size (priv->dpb) > 0) {
      GstH264Picture *prev_picture;
      GArray *pictures;

      pictures = gst_h264_dpb_get_pictures_all (priv->dpb);
      prev_picture =
          g_array_index (pictures, GstH264Picture *, pictures->len - 1);
      g_array_unref (pictures); /* prev_picture should be held */

      /* Previous picture was a field picture. */
      if (!GST_H264_PICTURE_IS_FRAME (prev_picture)
          && !prev_picture->other_field) {
        prev_field = prev_picture;
        in_dpb = TRUE;
      }
    }
  } else {
    g_assert (priv->last_field == NULL);
  }

  /* This is not a field picture */
  if (!slice_hdr->field_pic_flag) {
    if (!prev_field)
      return TRUE;

    GST_WARNING_OBJECT (self, "Previous picture %p (poc %d) is not complete",
        prev_field, prev_field->pic_order_cnt);
    goto error;
  }

  /* OK, this is the first field. */
  if (!prev_field)
    return TRUE;

  if (prev_field->frame_num != slice_hdr->frame_num) {
    GST_WARNING_OBJECT (self, "Previous picture %p (poc %d) is not complete",
        prev_field, prev_field->pic_order_cnt);
    goto error;
  } else {
    GstH264PictureField current_field = slice_hdr->bottom_field_flag ?
        GST_H264_PICTURE_FIELD_BOTTOM_FIELD : GST_H264_PICTURE_FIELD_TOP_FIELD;

    if (current_field == prev_field->field) {
      GST_WARNING_OBJECT (self,
          "Currnet picture and previous picture have identical field %d",
          current_field);
      goto error;
    }
  }

  *first_field = gst_h264_picture_ref (prev_field);
  return TRUE;

error:
  if (!in_dpb) {
    gst_clear_h264_picture (&priv->last_field);
  } else {
    /* FIXME: implement fill gap field picture if it is already in DPB */
  }

  return FALSE;
}

static GstFlowReturn
gst_h264_decoder_parse_slice (GstH264Decoder * self, GstH264NalUnit * nalu)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264ParserResult pres = GST_H264_PARSER_OK;
  GstFlowReturn ret = GST_FLOW_OK;

  memset (&priv->current_slice, 0, sizeof (GstH264Slice));

  pres = gst_h264_parser_parse_slice_hdr (priv->parser, nalu,
      &priv->current_slice.header, TRUE, TRUE);

  if (pres != GST_H264_PARSER_OK) {
    GST_ERROR_OBJECT (self, "Failed to parse slice header, ret %d", pres);
    memset (&priv->current_slice, 0, sizeof (GstH264Slice));

    return GST_FLOW_ERROR;
  }

  priv->current_slice.nalu = *nalu;

  if (!gst_h264_decoder_preprocess_slice (self, &priv->current_slice))
    return GST_FLOW_ERROR;

  priv->active_pps = priv->current_slice.header.pps;
  priv->active_sps = priv->active_pps->sequence;

  /* Check whether field picture boundary within given codec frame.
   * This might happen in case that upstream sent buffer per frame unit,
   * not picture unit (i.e., AU unit).
   * If AU boundary is detected, then finish first field picture we decoded
   * in this chain, we should finish the current picture and
   * start new field picture decoding */
  if (gst_h264_dpb_get_interlaced (priv->dpb) && priv->current_picture &&
      !GST_H264_PICTURE_IS_FRAME (priv->current_picture) &&
      !priv->current_picture->second_field) {
    GstH264PictureField prev_field = priv->current_picture->field;
    GstH264PictureField cur_field = GST_H264_PICTURE_FIELD_FRAME;
    if (priv->current_slice.header.field_pic_flag)
      cur_field = priv->current_slice.header.bottom_field_flag ?
          GST_H264_PICTURE_FIELD_BOTTOM_FIELD :
          GST_H264_PICTURE_FIELD_TOP_FIELD;

    if (cur_field != prev_field) {
      GST_LOG_OBJECT (self,
          "Found new field picture, finishing the first field picture");
      gst_h264_decoder_finish_current_picture (self, &ret);
    }
  }

  if (!priv->current_picture) {
    GstH264DecoderClass *klass = GST_H264_DECODER_GET_CLASS (self);
    GstH264Picture *picture = NULL;
    GstH264Picture *first_field = NULL;
    GstFlowReturn ret = GST_FLOW_OK;

    g_assert (priv->current_frame);

    if (!gst_h264_decoder_find_first_field_picture (self,
            &priv->current_slice, &first_field)) {
      GST_ERROR_OBJECT (self, "Couldn't find or determine first picture");
      return GST_FLOW_ERROR;
    }

    if (first_field) {
      picture = gst_h264_decoder_new_field_picture (self, first_field);
      gst_h264_picture_unref (first_field);

      if (!picture) {
        GST_ERROR_OBJECT (self, "Couldn't duplicate the first field picture");
        return GST_FLOW_ERROR;
      }
    } else {
      picture = gst_h264_picture_new ();

      if (klass->new_picture)
        ret = klass->new_picture (self, priv->current_frame, picture);

      if (ret != GST_FLOW_OK) {
        GST_WARNING_OBJECT (self, "subclass does not want accept new picture");
        priv->current_picture = NULL;
        gst_h264_picture_unref (picture);
        return ret;
      }

      priv->last_reorder_frame_number++;
      picture->reorder_frame_number = priv->last_reorder_frame_number;
    }

    /* This allows accessing the frame from the picture. */
    GST_CODEC_PICTURE_FRAME_NUMBER (picture) =
        priv->current_frame->system_frame_number;
    priv->current_picture = picture;

    ret = gst_h264_decoder_start_current_picture (self);
    if (ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT (self, "start picture failed");
      return ret;
    }
  }

  return gst_h264_decoder_decode_slice (self);
}

static GstFlowReturn
gst_h264_decoder_decode_nal (GstH264Decoder * self, GstH264NalUnit * nalu)
{
  GstFlowReturn ret = GST_FLOW_OK;

  GST_LOG_OBJECT (self, "Parsed nal type: %d, offset %d, size %d",
      nalu->type, nalu->offset, nalu->size);

  switch (nalu->type) {
    case GST_H264_NAL_SPS:
      ret = gst_h264_decoder_parse_sps (self, nalu);
      break;
    case GST_H264_NAL_PPS:
      ret = gst_h264_decoder_parse_pps (self, nalu);
      break;
    case GST_H264_NAL_SLICE:
    case GST_H264_NAL_SLICE_DPA:
    case GST_H264_NAL_SLICE_DPB:
    case GST_H264_NAL_SLICE_DPC:
    case GST_H264_NAL_SLICE_IDR:
    case GST_H264_NAL_SLICE_EXT:
      ret = gst_h264_decoder_parse_slice (self, nalu);
      break;
    default:
      break;
  }

  return ret;
}

static void
gst_h264_decoder_format_from_caps (GstH264Decoder * self, GstCaps * caps,
    GstH264DecoderFormat * format, GstH264DecoderAlign * align)
{
  if (format)
    *format = GST_H264_DECODER_FORMAT_NONE;

  if (align)
    *align = GST_H264_DECODER_ALIGN_NONE;

  if (!gst_caps_is_fixed (caps)) {
    GST_WARNING_OBJECT (self, "Caps wasn't fixed");
    return;
  }

  GST_DEBUG_OBJECT (self, "parsing caps: %" GST_PTR_FORMAT, caps);

  if (caps && gst_caps_get_size (caps) > 0) {
    GstStructure *s = gst_caps_get_structure (caps, 0);
    const gchar *str = NULL;

    if (format) {
      if ((str = gst_structure_get_string (s, "stream-format"))) {
        if (strcmp (str, "avc") == 0 || strcmp (str, "avc3") == 0)
          *format = GST_H264_DECODER_FORMAT_AVC;
        else if (strcmp (str, "byte-stream") == 0)
          *format = GST_H264_DECODER_FORMAT_BYTE;
      }
    }

    if (align) {
      if ((str = gst_structure_get_string (s, "alignment"))) {
        if (strcmp (str, "au") == 0)
          *align = GST_H264_DECODER_ALIGN_AU;
        else if (strcmp (str, "nal") == 0)
          *align = GST_H264_DECODER_ALIGN_NAL;
      }
    }
  }
}

static gboolean
gst_h264_decoder_set_format (GstVideoDecoder * decoder,
    GstVideoCodecState * state)
{
  GstH264Decoder *self = GST_H264_DECODER (decoder);
  GstH264DecoderPrivate *priv = self->priv;
  GstQuery *query;

  GST_DEBUG_OBJECT (decoder, "Set format");

  priv->input_state_changed = TRUE;

  if (self->input_state)
    gst_video_codec_state_unref (self->input_state);

  self->input_state = gst_video_codec_state_ref (state);

  /* in case live streaming, we will run on low-latency mode */
  priv->is_live = FALSE;
  query = gst_query_new_latency ();
  if (gst_pad_peer_query (GST_VIDEO_DECODER_SINK_PAD (self), query))
    gst_query_parse_latency (query, &priv->is_live, NULL, NULL);
  gst_query_unref (query);

  if (priv->is_live)
    GST_DEBUG_OBJECT (self, "Live source, will run on low-latency mode");

  if (state->caps) {
    GstH264DecoderFormat format;
    GstH264DecoderAlign align;

    gst_h264_decoder_format_from_caps (self, state->caps, &format, &align);

    if (format == GST_H264_DECODER_FORMAT_NONE) {
      /* codec_data implies avc */
      if (state->codec_data) {
        GST_WARNING_OBJECT (self,
            "video/x-h264 caps with codec_data but no stream-format=avc");
        format = GST_H264_DECODER_FORMAT_AVC;
      } else {
        /* otherwise assume bytestream input */
        GST_WARNING_OBJECT (self,
            "video/x-h264 caps without codec_data or stream-format");
        format = GST_H264_DECODER_FORMAT_BYTE;
      }
    }

    if (format == GST_H264_DECODER_FORMAT_AVC) {
      /* AVC requires codec_data, AVC3 might have one and/or SPS/PPS inline */
      if (!state->codec_data) {
        /* Try it with size 4 anyway */
        priv->nal_length_size = 4;
        GST_WARNING_OBJECT (self,
            "avc format without codec data, assuming nal length size is 4");
      }

      /* AVC implies alignment=au */
      if (align == GST_H264_DECODER_ALIGN_NONE)
        align = GST_H264_DECODER_ALIGN_AU;
    }

    if (format == GST_H264_DECODER_FORMAT_BYTE && state->codec_data)
      GST_WARNING_OBJECT (self, "bytestream with codec data");

    priv->in_format = format;
    priv->align = align;
  }

  if (state->codec_data) {
    GstMapInfo map;

    gst_buffer_map (state->codec_data, &map, GST_MAP_READ);
    if (gst_h264_decoder_parse_codec_data (self, map.data, map.size) !=
        GST_FLOW_OK) {
      /* keep going without error.
       * Probably inband SPS/PPS might be valid data */
      GST_WARNING_OBJECT (self, "Failed to handle codec data");
    }
    gst_buffer_unmap (state->codec_data, &map);
  }

  return TRUE;
}

static gboolean
gst_h264_decoder_negotiate (GstVideoDecoder * decoder)
{
  GstH264Decoder *self = GST_H264_DECODER (decoder);

  /* output state must be updated by subclass using new input state already */
  self->priv->input_state_changed = FALSE;

  return GST_VIDEO_DECODER_CLASS (parent_class)->negotiate (decoder);
}

static gboolean
gst_h264_decoder_fill_picture_from_slice (GstH264Decoder * self,
    const GstH264Slice * slice, GstH264Picture * picture)
{
  GstH264DecoderClass *klass = GST_H264_DECODER_GET_CLASS (self);
  const GstH264SliceHdr *slice_hdr = &slice->header;
  const GstH264PPS *pps;
  const GstH264SPS *sps;

  pps = slice_hdr->pps;
  if (!pps) {
    GST_ERROR_OBJECT (self, "No pps in slice header");
    return FALSE;
  }

  sps = pps->sequence;
  if (!sps) {
    GST_ERROR_OBJECT (self, "No sps in pps");
    return FALSE;
  }

  picture->idr = slice->nalu.idr_pic_flag;
  picture->dec_ref_pic_marking = slice_hdr->dec_ref_pic_marking;
  picture->field_pic_flag = slice_hdr->field_pic_flag;

  if (picture->idr)
    picture->idr_pic_id = slice_hdr->idr_pic_id;

  if (slice_hdr->field_pic_flag)
    picture->field =
        slice_hdr->bottom_field_flag ?
        GST_H264_PICTURE_FIELD_BOTTOM_FIELD : GST_H264_PICTURE_FIELD_TOP_FIELD;
  else
    picture->field = GST_H264_PICTURE_FIELD_FRAME;

  if (!GST_H264_PICTURE_IS_FRAME (picture) && !klass->new_field_picture) {
    GST_FIXME_OBJECT (self, "Subclass doesn't support interlace stream");
    return FALSE;
  }

  picture->nal_ref_idc = slice->nalu.ref_idc;
  if (slice->nalu.ref_idc != 0)
    gst_h264_picture_set_reference (picture,
        GST_H264_PICTURE_REF_SHORT_TERM, FALSE);

  picture->frame_num = slice_hdr->frame_num;

  /* 7.4.3 */
  if (!slice_hdr->field_pic_flag)
    picture->pic_num = slice_hdr->frame_num;
  else
    picture->pic_num = 2 * slice_hdr->frame_num + 1;

  picture->pic_order_cnt_type = sps->pic_order_cnt_type;
  switch (picture->pic_order_cnt_type) {
    case 0:
      picture->pic_order_cnt_lsb = slice_hdr->pic_order_cnt_lsb;
      picture->delta_pic_order_cnt_bottom =
          slice_hdr->delta_pic_order_cnt_bottom;
      break;
    case 1:
      picture->delta_pic_order_cnt0 = slice_hdr->delta_pic_order_cnt[0];
      picture->delta_pic_order_cnt1 = slice_hdr->delta_pic_order_cnt[1];
      break;
    case 2:
      break;
    default:
      g_assert_not_reached ();
      return FALSE;
  }

  return TRUE;
}

static gboolean
gst_h264_decoder_calculate_poc (GstH264Decoder * self, GstH264Picture * picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  const GstH264SPS *sps = priv->active_sps;

  if (!sps) {
    GST_ERROR_OBJECT (self, "No active SPS");
    return FALSE;
  }

  switch (picture->pic_order_cnt_type) {
    case 0:{
      /* See spec 8.2.1.1 */
      gint prev_pic_order_cnt_msb, prev_pic_order_cnt_lsb;
      gint max_pic_order_cnt_lsb;

      if (picture->idr) {
        prev_pic_order_cnt_msb = prev_pic_order_cnt_lsb = 0;
      } else {
        if (priv->prev_ref_has_memmgmnt5) {
          if (priv->prev_ref_field != GST_H264_PICTURE_FIELD_BOTTOM_FIELD) {
            prev_pic_order_cnt_msb = 0;
            prev_pic_order_cnt_lsb = priv->prev_ref_top_field_order_cnt;
          } else {
            prev_pic_order_cnt_msb = 0;
            prev_pic_order_cnt_lsb = 0;
          }
        } else {
          prev_pic_order_cnt_msb = priv->prev_ref_pic_order_cnt_msb;
          prev_pic_order_cnt_lsb = priv->prev_ref_pic_order_cnt_lsb;
        }
      }

      max_pic_order_cnt_lsb = 1 << (sps->log2_max_pic_order_cnt_lsb_minus4 + 4);

      if ((picture->pic_order_cnt_lsb < prev_pic_order_cnt_lsb) &&
          (prev_pic_order_cnt_lsb - picture->pic_order_cnt_lsb >=
              max_pic_order_cnt_lsb / 2)) {
        picture->pic_order_cnt_msb =
            prev_pic_order_cnt_msb + max_pic_order_cnt_lsb;
      } else if ((picture->pic_order_cnt_lsb > prev_pic_order_cnt_lsb)
          && (picture->pic_order_cnt_lsb - prev_pic_order_cnt_lsb >
              max_pic_order_cnt_lsb / 2)) {
        picture->pic_order_cnt_msb =
            prev_pic_order_cnt_msb - max_pic_order_cnt_lsb;
      } else {
        picture->pic_order_cnt_msb = prev_pic_order_cnt_msb;
      }

      if (picture->field != GST_H264_PICTURE_FIELD_BOTTOM_FIELD) {
        picture->top_field_order_cnt =
            picture->pic_order_cnt_msb + picture->pic_order_cnt_lsb;
      }

      switch (picture->field) {
        case GST_H264_PICTURE_FIELD_FRAME:
          picture->top_field_order_cnt = picture->pic_order_cnt_msb +
              picture->pic_order_cnt_lsb;
          picture->bottom_field_order_cnt = picture->top_field_order_cnt +
              picture->delta_pic_order_cnt_bottom;
          break;
        case GST_H264_PICTURE_FIELD_TOP_FIELD:
          picture->top_field_order_cnt = picture->pic_order_cnt_msb +
              picture->pic_order_cnt_lsb;
          break;
        case GST_H264_PICTURE_FIELD_BOTTOM_FIELD:
          picture->bottom_field_order_cnt = picture->pic_order_cnt_msb +
              picture->pic_order_cnt_lsb;
          break;
      }
      break;
    }

    case 1:{
      gint abs_frame_num = 0;
      gint expected_pic_order_cnt = 0;
      gint i;

      /* See spec 8.2.1.2 */
      if (priv->prev_has_memmgmnt5)
        priv->prev_frame_num_offset = 0;

      if (picture->idr)
        picture->frame_num_offset = 0;
      else if (priv->prev_frame_num > picture->frame_num)
        picture->frame_num_offset =
            priv->prev_frame_num_offset + priv->max_frame_num;
      else
        picture->frame_num_offset = priv->prev_frame_num_offset;

      if (sps->num_ref_frames_in_pic_order_cnt_cycle != 0)
        abs_frame_num = picture->frame_num_offset + picture->frame_num;
      else
        abs_frame_num = 0;

      if (picture->nal_ref_idc == 0 && abs_frame_num > 0)
        --abs_frame_num;

      if (abs_frame_num > 0) {
        gint pic_order_cnt_cycle_cnt, frame_num_in_pic_order_cnt_cycle;
        gint expected_delta_per_pic_order_cnt_cycle = 0;

        if (sps->num_ref_frames_in_pic_order_cnt_cycle == 0) {
          GST_WARNING_OBJECT (self,
              "Invalid num_ref_frames_in_pic_order_cnt_cycle in stream");
          return FALSE;
        }

        pic_order_cnt_cycle_cnt =
            (abs_frame_num - 1) / sps->num_ref_frames_in_pic_order_cnt_cycle;
        frame_num_in_pic_order_cnt_cycle =
            (abs_frame_num - 1) % sps->num_ref_frames_in_pic_order_cnt_cycle;

        for (i = 0; i < sps->num_ref_frames_in_pic_order_cnt_cycle; i++) {
          expected_delta_per_pic_order_cnt_cycle +=
              sps->offset_for_ref_frame[i];
        }

        expected_pic_order_cnt = pic_order_cnt_cycle_cnt *
            expected_delta_per_pic_order_cnt_cycle;
        /* frame_num_in_pic_order_cnt_cycle is verified < 255 in parser */
        for (i = 0; i <= frame_num_in_pic_order_cnt_cycle; ++i)
          expected_pic_order_cnt += sps->offset_for_ref_frame[i];
      }

      if (!picture->nal_ref_idc)
        expected_pic_order_cnt += sps->offset_for_non_ref_pic;

      if (GST_H264_PICTURE_IS_FRAME (picture)) {
        picture->top_field_order_cnt =
            expected_pic_order_cnt + picture->delta_pic_order_cnt0;
        picture->bottom_field_order_cnt = picture->top_field_order_cnt +
            sps->offset_for_top_to_bottom_field + picture->delta_pic_order_cnt1;
      } else if (picture->field != GST_H264_PICTURE_FIELD_BOTTOM_FIELD) {
        picture->top_field_order_cnt =
            expected_pic_order_cnt + picture->delta_pic_order_cnt0;
      } else {
        picture->bottom_field_order_cnt = expected_pic_order_cnt +
            sps->offset_for_top_to_bottom_field + picture->delta_pic_order_cnt0;
      }
      break;
    }

    case 2:{
      gint temp_pic_order_cnt;

      /* See spec 8.2.1.3 */
      if (priv->prev_has_memmgmnt5)
        priv->prev_frame_num_offset = 0;

      if (picture->idr)
        picture->frame_num_offset = 0;
      else if (priv->prev_frame_num > picture->frame_num)
        picture->frame_num_offset =
            priv->prev_frame_num_offset + priv->max_frame_num;
      else
        picture->frame_num_offset = priv->prev_frame_num_offset;

      if (picture->idr) {
        temp_pic_order_cnt = 0;
      } else if (!picture->nal_ref_idc) {
        temp_pic_order_cnt =
            2 * (picture->frame_num_offset + picture->frame_num) - 1;
      } else {
        temp_pic_order_cnt =
            2 * (picture->frame_num_offset + picture->frame_num);
      }

      if (GST_H264_PICTURE_IS_FRAME (picture)) {
        picture->top_field_order_cnt = temp_pic_order_cnt;
        picture->bottom_field_order_cnt = temp_pic_order_cnt;
      } else if (picture->field == GST_H264_PICTURE_FIELD_BOTTOM_FIELD) {
        picture->bottom_field_order_cnt = temp_pic_order_cnt;
      } else {
        picture->top_field_order_cnt = temp_pic_order_cnt;
      }
      break;
    }

    default:
      GST_WARNING_OBJECT (self,
          "Invalid pic_order_cnt_type: %d", sps->pic_order_cnt_type);
      return FALSE;
  }

  switch (picture->field) {
    case GST_H264_PICTURE_FIELD_FRAME:
      picture->pic_order_cnt =
          MIN (picture->top_field_order_cnt, picture->bottom_field_order_cnt);
      break;
    case GST_H264_PICTURE_FIELD_TOP_FIELD:
      picture->pic_order_cnt = picture->top_field_order_cnt;
      break;
    case GST_H264_PICTURE_FIELD_BOTTOM_FIELD:
      picture->pic_order_cnt = picture->bottom_field_order_cnt;
      break;
    default:
      g_assert_not_reached ();
      return FALSE;
  }

  return TRUE;
}

static void
gst_h264_decoder_drain_output_queue (GstH264Decoder * self, guint num,
    GstFlowReturn * ret)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264DecoderClass *klass = GST_H264_DECODER_GET_CLASS (self);

  g_assert (klass->output_picture);
  g_assert (ret != NULL);

  while (gst_queue_array_get_length (priv->output_queue) > num) {
    GstH264DecoderOutputFrame *output_frame = (GstH264DecoderOutputFrame *)
        gst_queue_array_pop_head_struct (priv->output_queue);
    GstFlowReturn flow_ret = klass->output_picture (self, output_frame->frame,
        output_frame->picture);

    UPDATE_FLOW_RETURN (ret, flow_ret);
  }
}

static void
gst_h264_decoder_do_output_picture (GstH264Decoder * self,
    GstH264Picture * picture, GstFlowReturn * ret)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstVideoCodecFrame *frame = NULL;
  GstH264DecoderOutputFrame output_frame;
#ifndef GST_DISABLE_GST_DEBUG
  guint32 last_output_poc;
#endif

  g_assert (ret != NULL);

  GST_LOG_OBJECT (self, "Outputting picture %p (frame_num %d, poc %d)",
      picture, picture->frame_num, picture->pic_order_cnt);

#ifndef GST_DISABLE_GST_DEBUG
  last_output_poc = gst_h264_dpb_get_last_output_poc (priv->dpb);
  if (picture->pic_order_cnt < last_output_poc) {
    GST_WARNING_OBJECT (self,
        "Outputting out of order %d -> %d, likely a broken stream",
        last_output_poc, picture->pic_order_cnt);
  }
#endif

  if (priv->last_reorder_frame_number > picture->reorder_frame_number) {
    guint64 diff = priv->last_reorder_frame_number -
        picture->reorder_frame_number;
    guint64 total_delay = diff + priv->preferred_output_delay;
    if (diff > priv->max_reorder_count && total_delay < G_MAXUINT32) {
      GstClockTime latency;

      priv->max_reorder_count = (guint32) diff;
      latency = gst_util_uint64_scale_int (GST_SECOND * total_delay,
          priv->fps_d, priv->fps_n);

      if (latency != G_MAXUINT64) {
        GST_DEBUG_OBJECT (self, "Updating latency to %" GST_TIME_FORMAT
            ", reorder count: %" G_GUINT64_FORMAT ", output-delay: %u",
            GST_TIME_ARGS (latency), diff, priv->preferred_output_delay);

        gst_video_decoder_set_latency (GST_VIDEO_DECODER (self),
            latency, latency);
      }
    }
  }

  frame = gst_video_decoder_get_frame (GST_VIDEO_DECODER (self),
      GST_CODEC_PICTURE_FRAME_NUMBER (picture));

  if (!frame) {
    /* The case where the end_picture() got failed and corresponding
     * GstVideoCodecFrame was dropped already */
    if (picture->nonexisting) {
      GST_DEBUG_OBJECT (self, "Dropping non-existing picture %p", picture);
    } else {
      GST_ERROR_OBJECT (self,
          "No available codec frame with frame number %d",
          GST_CODEC_PICTURE_FRAME_NUMBER (picture));
      UPDATE_FLOW_RETURN (ret, GST_FLOW_ERROR);
    }

    gst_h264_picture_unref (picture);

    return;
  }

  output_frame.frame = frame;
  output_frame.picture = picture;
  output_frame.self = self;
  gst_queue_array_push_tail_struct (priv->output_queue, &output_frame);

  gst_h264_decoder_drain_output_queue (self, priv->preferred_output_delay,
      &priv->last_flow);
}

static void
gst_h264_decoder_finish_current_picture (GstH264Decoder * self,
    GstFlowReturn * ret)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264DecoderClass *klass;
  GstFlowReturn flow_ret = GST_FLOW_OK;

  if (!priv->current_picture) {
    if (priv->current_frame) {
      GST_DEBUG_OBJECT (self,
          "AU buffer without slice data, releasing current frame %u",
          priv->current_frame->system_frame_number);
      gst_video_decoder_release_frame (GST_VIDEO_DECODER_CAST (self),
          gst_video_codec_frame_ref (priv->current_frame));
    }
    return;
  }

  klass = GST_H264_DECODER_GET_CLASS (self);

  if (klass->end_picture) {
    flow_ret = klass->end_picture (self, priv->current_picture);
    if (flow_ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT (self,
          "end picture failed, marking picture %p non-existing "
          "(frame_num %d, poc %d)", priv->current_picture,
          priv->current_picture->frame_num,
          priv->current_picture->pic_order_cnt);
      priv->current_picture->nonexisting = TRUE;

      /* this fake nonexisting picture will not trigger ouput_picture() */
      gst_video_decoder_release_frame (GST_VIDEO_DECODER (self),
          gst_video_codec_frame_ref (priv->current_frame));
    }
  }

  /* We no longer need the per frame reference lists */
  gst_h264_decoder_clear_ref_pic_lists (self);

  /* finish picture takes ownership of the picture */
  gst_h264_decoder_finish_picture (self, priv->current_picture, &flow_ret);
  priv->current_picture = NULL;

  UPDATE_FLOW_RETURN (ret, flow_ret);
}

static gint
poc_asc_compare (const GstH264Picture ** a, const GstH264Picture ** b)
{
  return (*a)->pic_order_cnt - (*b)->pic_order_cnt;
}

static gint
poc_desc_compare (const GstH264Picture ** a, const GstH264Picture ** b)
{
  return (*b)->pic_order_cnt - (*a)->pic_order_cnt;
}

static GstFlowReturn
gst_h264_decoder_drain_internal (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264Picture *picture;
  GstFlowReturn ret = GST_FLOW_OK;

  while ((picture = gst_h264_dpb_bump (priv->dpb, TRUE)) != NULL) {
    gst_h264_decoder_do_output_picture (self, picture, &ret);
  }

  gst_h264_decoder_drain_output_queue (self, 0, &ret);

  gst_clear_h264_picture (&priv->last_field);
  gst_h264_dpb_clear (priv->dpb);

  return ret;
}

static gboolean
gst_h264_decoder_handle_memory_management_opt (GstH264Decoder * self,
    GstH264Picture * picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  gint i;

  for (i = 0; i < G_N_ELEMENTS (picture->dec_ref_pic_marking.ref_pic_marking);
      i++) {
    GstH264RefPicMarking *ref_pic_marking =
        &picture->dec_ref_pic_marking.ref_pic_marking[i];
    guint8 type = ref_pic_marking->memory_management_control_operation;

    GST_TRACE_OBJECT (self, "memory management operation %d, type %d", i, type);

    /* Normal end of operations' specification */
    if (type == 0)
      return TRUE;

    switch (type) {
      case 4:
        priv->max_long_term_frame_idx =
            ref_pic_marking->max_long_term_frame_idx_plus1 - 1;
        break;
      case 5:
        priv->max_long_term_frame_idx = -1;
        break;
      default:
        break;
    }

    if (!gst_h264_dpb_perform_memory_management_control_operation (priv->dpb,
            ref_pic_marking, picture)) {
      GST_WARNING_OBJECT (self, "memory management operation type %d failed",
          type);
      /* Most likely our implementation fault, but let's just perform
       * next MMCO if any */
    }
  }

  return TRUE;
}

static gboolean
gst_h264_decoder_sliding_window_picture_marking (GstH264Decoder * self,
    GstH264Picture * picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  const GstH264SPS *sps = priv->active_sps;
  gint num_ref_pics;
  gint max_num_ref_frames;

  /* Skip this for the second field */
  if (picture->second_field)
    return TRUE;

  if (!sps) {
    GST_ERROR_OBJECT (self, "No active sps");
    return FALSE;
  }

  /* 8.2.5.3. Ensure the DPB doesn't overflow by discarding the oldest picture */
  num_ref_pics = gst_h264_dpb_num_ref_frames (priv->dpb);
  max_num_ref_frames = MAX (1, sps->num_ref_frames);

  if (num_ref_pics < max_num_ref_frames)
    return TRUE;

  /* In theory, num_ref_pics shouldn't be larger than max_num_ref_frames
   * but it could happen if our implementation is wrong somehow or so.
   * Just try to remove reference pictures as many as possible in order to
   * avoid DPB overflow.
   */
  while (num_ref_pics >= max_num_ref_frames) {
    /* Max number of reference pics reached, need to remove one of the short
     * term ones. Find smallest frame_num_wrap short reference picture and mark
     * it as unused */
    GstH264Picture *to_unmark =
        gst_h264_dpb_get_lowest_frame_num_short_ref (priv->dpb);

    if (num_ref_pics > max_num_ref_frames) {
      GST_WARNING_OBJECT (self,
          "num_ref_pics %d is larger than allowed maximum %d",
          num_ref_pics, max_num_ref_frames);
    }

    if (!to_unmark) {
      GST_WARNING_OBJECT (self, "Could not find a short ref picture to unmark");
      return FALSE;
    }

    GST_TRACE_OBJECT (self,
        "Unmark reference flag of picture %p (frame_num %d, poc %d)",
        to_unmark, to_unmark->frame_num, to_unmark->pic_order_cnt);

    gst_h264_picture_set_reference (to_unmark, GST_H264_PICTURE_REF_NONE, TRUE);
    gst_h264_picture_unref (to_unmark);

    num_ref_pics--;
  }

  return TRUE;
}

/* This method ensures that DPB does not overflow, either by removing
 * reference pictures as specified in the stream, or using a sliding window
 * procedure to remove the oldest one.
 * It also performs marking and unmarking pictures as reference.
 * See spac 8.2.5.1 */
static gboolean
gst_h264_decoder_reference_picture_marking (GstH264Decoder * self,
    GstH264Picture * picture)
{
  GstH264DecoderPrivate *priv = self->priv;

  /* If the current picture is an IDR, all reference pictures are unmarked */
  if (picture->idr) {
    gst_h264_dpb_mark_all_non_ref (priv->dpb);

    if (picture->dec_ref_pic_marking.long_term_reference_flag) {
      gst_h264_picture_set_reference (picture,
          GST_H264_PICTURE_REF_LONG_TERM, FALSE);
      picture->long_term_frame_idx = 0;
      priv->max_long_term_frame_idx = 0;
    } else {
      gst_h264_picture_set_reference (picture,
          GST_H264_PICTURE_REF_SHORT_TERM, FALSE);
      priv->max_long_term_frame_idx = -1;
    }

    return TRUE;
  }

  /* Not an IDR. If the stream contains instructions on how to discard pictures
   * from DPB and how to mark/unmark existing reference pictures, do so.
   * Otherwise, fall back to default sliding window process */
  if (picture->dec_ref_pic_marking.adaptive_ref_pic_marking_mode_flag) {
    if (picture->nonexisting) {
      GST_WARNING_OBJECT (self,
          "Invalid memory management operation for non-existing picture "
          "%p (frame_num %d, poc %d", picture, picture->frame_num,
          picture->pic_order_cnt);
    }

    return gst_h264_decoder_handle_memory_management_opt (self, picture);
  }

  return gst_h264_decoder_sliding_window_picture_marking (self, picture);
}

static GstH264DpbBumpMode
get_bump_level (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv = self->priv;

  /* User set the mode explicitly. */
  switch (priv->compliance) {
    case GST_H264_DECODER_COMPLIANCE_STRICT:
      return GST_H264_DPB_BUMP_NORMAL_LATENCY;
    case GST_H264_DECODER_COMPLIANCE_NORMAL:
      return GST_H264_DPB_BUMP_LOW_LATENCY;
    case GST_H264_DECODER_COMPLIANCE_FLEXIBLE:
      return GST_H264_DPB_BUMP_VERY_LOW_LATENCY;
    default:
      break;
  }

  /* GST_H264_DECODER_COMPLIANCE_AUTO case. */

  if (priv->is_live) {
    /* The baseline and constrained-baseline profiles do not have B frames
       and do not use the picture reorder, safe to use the higher bump level. */
    if (priv->profile_idc == GST_H264_PROFILE_BASELINE)
      return GST_H264_DPB_BUMP_VERY_LOW_LATENCY;

    return GST_H264_DPB_BUMP_LOW_LATENCY;
  }

  return GST_H264_DPB_BUMP_NORMAL_LATENCY;
}

static void
gst_h264_decoder_finish_picture (GstH264Decoder * self,
    GstH264Picture * picture, GstFlowReturn * ret)
{
  GstVideoDecoder *decoder = GST_VIDEO_DECODER (self);
  GstH264DecoderPrivate *priv = self->priv;
  GstH264DpbBumpMode bump_level = get_bump_level (self);

  /* Finish processing the picture.
   * Start by storing previous picture data for later use */
  if (picture->ref) {
    gst_h264_decoder_reference_picture_marking (self, picture);
    priv->prev_ref_has_memmgmnt5 = picture->mem_mgmt_5;
    priv->prev_ref_top_field_order_cnt = picture->top_field_order_cnt;
    priv->prev_ref_pic_order_cnt_msb = picture->pic_order_cnt_msb;
    priv->prev_ref_pic_order_cnt_lsb = picture->pic_order_cnt_lsb;
    priv->prev_ref_field = picture->field;
    priv->prev_ref_frame_num = picture->frame_num;
  }

  priv->prev_frame_num = picture->frame_num;
  priv->prev_has_memmgmnt5 = picture->mem_mgmt_5;
  priv->prev_frame_num_offset = picture->frame_num_offset;

  /* Remove unused (for reference or later output) pictures from DPB, marking
   * them as such */
  gst_h264_dpb_delete_unused (priv->dpb);

  /* If field pictures belong to different codec frame,
   * drop codec frame of the second field because we are consuming
   * only the first codec frame via GstH264Decoder::output_picture() method */
  if (picture->second_field && picture->other_field &&
      GST_CODEC_PICTURE_FRAME_NUMBER (picture) !=
      GST_CODEC_PICTURE_FRAME_NUMBER (picture->other_field)) {
    GstVideoCodecFrame *frame = gst_video_decoder_get_frame (decoder,
        GST_CODEC_PICTURE_FRAME_NUMBER (picture));

    gst_video_decoder_release_frame (decoder, frame);
  }

  /* C.4.4 */
  if (picture->mem_mgmt_5) {
    GstFlowReturn drain_ret;

    GST_TRACE_OBJECT (self, "Memory management type 5, drain the DPB");

    drain_ret = gst_h264_decoder_drain_internal (self);
    UPDATE_FLOW_RETURN (ret, drain_ret);
  }

  _bump_dpb (self, bump_level, picture, ret);

  /* Add a ref to avoid the case of directly outputed and destroyed. */
  gst_h264_picture_ref (picture);

  /* C.4.5.1, C.4.5.2
     - If the current decoded picture is the second field of a complementary
     reference field pair, add to DPB.
     C.4.5.1
     For A reference decoded picture, the "bumping" process is invoked
     repeatedly until there is an empty frame buffer, then add to DPB:
     C.4.5.2
     For a non-reference decoded picture, if there is empty frame buffer
     after bumping the smaller POC, add to DPB.
     Otherwise, output directly. */
  if ((picture->second_field && picture->other_field
          && picture->other_field->ref)
      || picture->ref || gst_h264_dpb_has_empty_frame_buffer (priv->dpb)) {
    /* Split frame into top/bottom field pictures for reference picture marking
     * process. Even if current picture has field_pic_flag equal to zero,
     * if next picture is a field picture, complementary field pair of reference
     * frame should have individual pic_num and long_term_pic_num.
     */
    if (gst_h264_dpb_get_interlaced (priv->dpb) &&
        GST_H264_PICTURE_IS_FRAME (picture)) {
      GstH264Picture *other_field =
          gst_h264_decoder_split_frame (self, picture);

      add_picture_to_dpb (self, picture);
      if (!other_field) {
        GST_WARNING_OBJECT (self,
            "Couldn't split frame into complementary field pair");
        /* Keep decoding anyway... */
      } else {
        add_picture_to_dpb (self, other_field);
      }
    } else {
      add_picture_to_dpb (self, picture);
    }
  } else {
    output_picture_directly (self, picture, ret);
  }

  GST_LOG_OBJECT (self,
      "Finishing picture %p (frame_num %d, poc %d), entries in DPB %d",
      picture, picture->frame_num, picture->pic_order_cnt,
      gst_h264_dpb_get_size (priv->dpb));

  gst_h264_picture_unref (picture);

  /* For low-latency output, we try to bump here to avoid waiting
   * for another decoding circle. */
  if (bump_level != GST_H264_DPB_BUMP_NORMAL_LATENCY)
    _bump_dpb (self, bump_level, NULL, ret);
}

static gint
gst_h264_decoder_get_max_num_reorder_frames (GstH264Decoder * self,
    GstH264SPS * sps, gint max_dpb_size)
{
  GstH264DecoderPrivate *priv = self->priv;

  if (sps->vui_parameters_present_flag
      && sps->vui_parameters.bitstream_restriction_flag) {
    if (sps->vui_parameters.num_reorder_frames > max_dpb_size) {
      GST_WARNING
          ("max_num_reorder_frames present, but larger than MaxDpbFrames (%d > %d)",
          sps->vui_parameters.num_reorder_frames, max_dpb_size);
      return max_dpb_size;
    }

    return sps->vui_parameters.num_reorder_frames;
  } else if (sps->constraint_set3_flag) {
    /* If max_num_reorder_frames is not present, if profile id is equal to
     * 44, 86, 100, 110, 122, or 244 and constraint_set3_flag is equal to 1,
     * max_num_reorder_frames shall be inferred to be equal to 0 */
    switch (sps->profile_idc) {
      case 44:
      case 86:
      case 100:
      case 110:
      case 122:
      case 244:
        return 0;
      default:
        break;
    }
  }

  /* Relaxed conditions (undefined by spec) */
  if (priv->compliance != GST_H264_DECODER_COMPLIANCE_STRICT &&
      (sps->profile_idc == 66 || sps->profile_idc == 83)) {
    /* baseline, constrained baseline and scalable-baseline profiles
     * only contain I/P frames. */
    return 0;
  }

  return max_dpb_size;
}

typedef struct
{
  GstH264Level level;

  guint32 max_mbps;
  guint32 max_fs;
  guint32 max_dpb_mbs;
  guint32 max_main_br;
} LevelLimits;

static const LevelLimits level_limits_map[] = {
  {GST_H264_LEVEL_L1, 1485, 99, 396, 64},
  {GST_H264_LEVEL_L1B, 1485, 99, 396, 128},
  {GST_H264_LEVEL_L1_1, 3000, 396, 900, 192},
  {GST_H264_LEVEL_L1_2, 6000, 396, 2376, 384},
  {GST_H264_LEVEL_L1_3, 11800, 396, 2376, 768},
  {GST_H264_LEVEL_L2, 11880, 396, 2376, 2000},
  {GST_H264_LEVEL_L2_1, 19800, 792, 4752, 4000},
  {GST_H264_LEVEL_L2_2, 20250, 1620, 8100, 4000},
  {GST_H264_LEVEL_L3, 40500, 1620, 8100, 10000},
  {GST_H264_LEVEL_L3_1, 108000, 3600, 18000, 14000},
  {GST_H264_LEVEL_L3_2, 216000, 5120, 20480, 20000},
  {GST_H264_LEVEL_L4, 245760, 8192, 32768, 20000},
  {GST_H264_LEVEL_L4_1, 245760, 8192, 32768, 50000},
  {GST_H264_LEVEL_L4_2, 522240, 8704, 34816, 50000},
  {GST_H264_LEVEL_L5, 589824, 22080, 110400, 135000},
  {GST_H264_LEVEL_L5_1, 983040, 36864, 184320, 240000},
  {GST_H264_LEVEL_L5_2, 2073600, 36864, 184320, 240000},
  {GST_H264_LEVEL_L6, 4177920, 139264, 696320, 240000},
  {GST_H264_LEVEL_L6_1, 8355840, 139264, 696320, 480000},
  {GST_H264_LEVEL_L6_2, 16711680, 139264, 696320, 800000}
};

static gint
h264_level_to_max_dpb_mbs (GstH264Level level)
{
  gint i;
  for (i = 0; i < G_N_ELEMENTS (level_limits_map); i++) {
    if (level == level_limits_map[i].level)
      return level_limits_map[i].max_dpb_mbs;
  }

  return 0;
}

static void
gst_h264_decoder_set_latency (GstH264Decoder * self, const GstH264SPS * sps,
    gint max_dpb_size)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstCaps *caps;
  GstClockTime min, max;
  GstStructure *structure;
  gint fps_d = 1, fps_n = 0;
  GstH264DpbBumpMode bump_level;
  guint32 frames_delay, max_frames_delay;

  caps = gst_pad_get_current_caps (GST_VIDEO_DECODER_SRC_PAD (self));
  if (!caps && self->input_state)
    caps = gst_caps_ref (self->input_state->caps);

  if (caps) {
    structure = gst_caps_get_structure (caps, 0);
    if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)) {
      if (fps_n == 0) {
        /* variable framerate: see if we have a max-framerate */
        gst_structure_get_fraction (structure, "max-framerate", &fps_n, &fps_d);
      }
    }
    gst_caps_unref (caps);
  }

  /* if no fps or variable, then 25/1 */
  if (fps_n == 0) {
    fps_n = 25;
    fps_d = 1;
  }

  frames_delay = max_dpb_size;

  bump_level = get_bump_level (self);
  if (bump_level != GST_H264_DPB_BUMP_NORMAL_LATENCY) {
    GST_DEBUG_OBJECT (self, "Actual latency will be updated later");
    frames_delay = 0;
  }

  priv->max_reorder_count = frames_delay;
  priv->fps_n = fps_n;
  priv->fps_d = fps_d;

  /* Consider output delay wanted by subclass */
  frames_delay += priv->preferred_output_delay;

  max_frames_delay = max_dpb_size + priv->preferred_output_delay;

  min = gst_util_uint64_scale_int (frames_delay * GST_SECOND, fps_d, fps_n);
  max = gst_util_uint64_scale_int (max_frames_delay * GST_SECOND, fps_d, fps_n);

  GST_DEBUG_OBJECT (self,
      "latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT
      ", frames-delay %d", GST_TIME_ARGS (min), GST_TIME_ARGS (max),
      frames_delay);

  gst_video_decoder_set_latency (GST_VIDEO_DECODER (self), min, max);
}

static GstFlowReturn
gst_h264_decoder_process_sps (GstH264Decoder * self, GstH264SPS * sps)
{
  GstH264DecoderClass *klass = GST_H264_DECODER_GET_CLASS (self);
  GstH264DecoderPrivate *priv = self->priv;
  guint8 level;
  gint max_dpb_mbs;
  gint width_mb, height_mb;
  gint max_dpb_frames;
  gint max_dpb_size;
  gint prev_max_dpb_size;
  gint max_reorder_frames;
  gint prev_max_reorder_frames;
  gboolean prev_interlaced;
  gboolean interlaced;
  GstFlowReturn ret = GST_FLOW_OK;

  if (sps->frame_mbs_only_flag == 0) {
    if (!klass->new_field_picture) {
      GST_FIXME_OBJECT (self,
          "frame_mbs_only_flag != 1 not supported by subclass");
      return GST_FLOW_NOT_NEGOTIATED;
    }

    if (sps->mb_adaptive_frame_field_flag) {
      GST_LOG_OBJECT (self,
          "mb_adaptive_frame_field_flag == 1, MBAFF sequence");
    } else {
      GST_LOG_OBJECT (self, "mb_adaptive_frame_field_flag == 0, PAFF sequence");
    }
  }

  interlaced = !sps->frame_mbs_only_flag;

  /* Spec A.3.1 and A.3.2
   * For Baseline, Constrained Baseline and Main profile, the indicated level is
   * Level 1b if level_idc is equal to 11 and constraint_set3_flag is equal to 1
   */
  level = sps->level_idc;
  if (level == 11 && (sps->profile_idc == 66 || sps->profile_idc == 77) &&
      sps->constraint_set3_flag) {
    /* Level 1b */
    level = 9;
  }

  max_dpb_mbs = h264_level_to_max_dpb_mbs ((GstH264Level) level);
  if (!max_dpb_mbs)
    return GST_FLOW_ERROR;

  width_mb = sps->width / 16;
  height_mb = sps->height / 16;

  max_dpb_frames = MIN (max_dpb_mbs / (width_mb * height_mb),
      GST_H264_DPB_MAX_SIZE);

  if (sps->vui_parameters_present_flag
      && sps->vui_parameters.bitstream_restriction_flag)
    max_dpb_frames = MAX (1, sps->vui_parameters.max_dec_frame_buffering);

  /* Case 1) There might be some non-conforming streams that require more DPB
   * size than that of specified one by SPS
   * Case 2) If bitstream_restriction_flag is not present,
   * max_dec_frame_buffering should be inferred
   * to be equal to MaxDpbFrames, then MaxDpbFrames can exceed num_ref_frames
   * See https://chromium-review.googlesource.com/c/chromium/src/+/760276/
   */
  max_dpb_size = MAX (max_dpb_frames, sps->num_ref_frames);
  if (max_dpb_size > GST_H264_DPB_MAX_SIZE) {
    GST_WARNING_OBJECT (self, "Too large calculated DPB size %d", max_dpb_size);
    max_dpb_size = GST_H264_DPB_MAX_SIZE;
  }

  /* Safety, so that subclass don't need bound checking */
  g_return_val_if_fail (max_dpb_size <= GST_H264_DPB_MAX_SIZE, GST_FLOW_ERROR);

  prev_max_dpb_size = gst_h264_dpb_get_max_num_frames (priv->dpb);
  prev_interlaced = gst_h264_dpb_get_interlaced (priv->dpb);

  prev_max_reorder_frames = gst_h264_dpb_get_max_num_reorder_frames (priv->dpb);
  max_reorder_frames =
      gst_h264_decoder_get_max_num_reorder_frames (self, sps, max_dpb_size);

  if (priv->width != sps->width || priv->height != sps->height ||
      prev_max_dpb_size != max_dpb_size || prev_interlaced != interlaced ||
      prev_max_reorder_frames != max_reorder_frames) {
    GstH264DecoderClass *klass = GST_H264_DECODER_GET_CLASS (self);

    GST_DEBUG_OBJECT (self,
        "SPS updated, resolution: %dx%d -> %dx%d, dpb size: %d -> %d, "
        "interlaced %d -> %d, max_reorder_frames: %d -> %d",
        priv->width, priv->height, sps->width, sps->height,
        prev_max_dpb_size, max_dpb_size, prev_interlaced, interlaced,
        prev_max_reorder_frames, max_reorder_frames);

    ret = gst_h264_decoder_drain (GST_VIDEO_DECODER (self));
    if (ret != GST_FLOW_OK)
      return ret;

    gst_h264_decoder_reset_latency_infos (self);

    g_assert (klass->new_sequence);

    if (klass->get_preferred_output_delay) {
      priv->preferred_output_delay =
          klass->get_preferred_output_delay (self, priv->is_live);
    } else {
      priv->preferred_output_delay = 0;
    }

    ret = klass->new_sequence (self,
        sps, max_dpb_size + priv->preferred_output_delay);
    if (ret != GST_FLOW_OK) {
      GST_WARNING_OBJECT (self, "subclass does not want accept new sequence");
      return ret;
    }

    priv->profile_idc = sps->profile_idc;
    priv->width = sps->width;
    priv->height = sps->height;

    gst_h264_dpb_set_max_num_frames (priv->dpb, max_dpb_size);
    gst_h264_dpb_set_interlaced (priv->dpb, interlaced);
    gst_h264_dpb_set_max_num_reorder_frames (priv->dpb, max_reorder_frames);
    gst_h264_decoder_set_latency (self, sps, max_dpb_size);
  }

  return GST_FLOW_OK;
}

static gboolean
gst_h264_decoder_init_gap_picture (GstH264Decoder * self,
    GstH264Picture * picture, gint frame_num)
{
  picture->nonexisting = TRUE;
  picture->nal_ref_idc = 1;
  picture->frame_num = picture->pic_num = frame_num;
  picture->dec_ref_pic_marking.adaptive_ref_pic_marking_mode_flag = FALSE;
  picture->ref = GST_H264_PICTURE_REF_SHORT_TERM;
  picture->ref_pic = TRUE;
  picture->dec_ref_pic_marking.long_term_reference_flag = FALSE;
  picture->field = GST_H264_PICTURE_FIELD_FRAME;

  return gst_h264_decoder_calculate_poc (self, picture);
}

static GstFlowReturn
gst_h264_decoder_decode_slice (GstH264Decoder * self)
{
  GstH264DecoderClass *klass = GST_H264_DECODER_GET_CLASS (self);
  GstH264DecoderPrivate *priv = self->priv;
  GstH264Slice *slice = &priv->current_slice;
  GstH264Picture *picture = priv->current_picture;
  GArray *ref_pic_list0 = NULL;
  GArray *ref_pic_list1 = NULL;
  GstFlowReturn ret = GST_FLOW_OK;

  if (!picture) {
    GST_ERROR_OBJECT (self, "No current picture");
    return GST_FLOW_ERROR;
  }

  GST_LOG_OBJECT (self, "Decode picture %p (frame_num %d, poc %d)",
      picture, picture->frame_num, picture->pic_order_cnt);

  priv->max_pic_num = slice->header.max_pic_num;

  if (priv->process_ref_pic_lists) {
    if (!gst_h264_decoder_modify_ref_pic_lists (self)) {
      ret = GST_FLOW_ERROR;
      goto beach;
    }

    ref_pic_list0 = priv->ref_pic_list0;
    ref_pic_list1 = priv->ref_pic_list1;
  }

  g_assert (klass->decode_slice);

  ret = klass->decode_slice (self, picture, slice, ref_pic_list0,
      ref_pic_list1);
  if (ret != GST_FLOW_OK) {
    GST_WARNING_OBJECT (self,
        "Subclass didn't want to decode picture %p (frame_num %d, poc %d)",
        picture, picture->frame_num, picture->pic_order_cnt);
  }

beach:
  g_array_set_size (priv->ref_pic_list0, 0);
  g_array_set_size (priv->ref_pic_list1, 0);

  return ret;
}

static gint
pic_num_desc_compare (const GstH264Picture ** a, const GstH264Picture ** b)
{
  return (*b)->pic_num - (*a)->pic_num;
}

static gint
long_term_pic_num_asc_compare (const GstH264Picture ** a,
    const GstH264Picture ** b)
{
  return (*a)->long_term_pic_num - (*b)->long_term_pic_num;
}

static void
construct_ref_pic_lists_p (GstH264Decoder * self,
    GstH264Picture * current_picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  gint pos;

  /* RefPicList0 (8.2.4.2.1) [[1] [2]], where:
   * [1] shortterm ref pics sorted by descending pic_num,
   * [2] longterm ref pics by ascending long_term_pic_num.
   */
  g_array_set_size (priv->ref_pic_list_p0, 0);

  gst_h264_dpb_get_pictures_short_term_ref (priv->dpb,
      TRUE, FALSE, priv->ref_pic_list_p0);
  g_array_sort (priv->ref_pic_list_p0, (GCompareFunc) pic_num_desc_compare);

  pos = priv->ref_pic_list_p0->len;
  gst_h264_dpb_get_pictures_long_term_ref (priv->dpb,
      FALSE, priv->ref_pic_list_p0);
  g_qsort_with_data (&g_array_index (priv->ref_pic_list_p0, gpointer, pos),
      priv->ref_pic_list_p0->len - pos, sizeof (gpointer),
      (GCompareDataFunc) long_term_pic_num_asc_compare, NULL);

#ifndef GST_DISABLE_GST_DEBUG
  if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) {
    GString *str = g_string_new (NULL);
    for (pos = 0; pos < priv->ref_pic_list_p0->len; pos++) {
      GstH264Picture *ref =
          g_array_index (priv->ref_pic_list_p0, GstH264Picture *, pos);
      if (!GST_H264_PICTURE_IS_LONG_TERM_REF (ref))
        g_string_append_printf (str, "|%i", ref->pic_num);
      else
        g_string_append_printf (str, "|%is", ref->pic_num);
    }
    GST_DEBUG_OBJECT (self, "ref_pic_list_p0: %s|", str->str);
    g_string_free (str, TRUE);
  }
#endif
}

static gint
frame_num_wrap_desc_compare (const GstH264Picture ** a,
    const GstH264Picture ** b)
{
  return (*b)->frame_num_wrap - (*a)->frame_num_wrap;
}

static gint
long_term_frame_idx_asc_compare (const GstH264Picture ** a,
    const GstH264Picture ** b)
{
  return (*a)->long_term_frame_idx - (*b)->long_term_frame_idx;
}

/* init_picture_refs_fields_1 in gstvaapidecoder_h264.c */
static void
init_picture_refs_fields_1 (GstH264Decoder * self, GstH264PictureField field,
    GArray * ref_frame_list, GArray * ref_pic_list_x)
{
  guint i = 0, j = 0;

  do {
    for (; i < ref_frame_list->len; i++) {
      GstH264Picture *pic = g_array_index (ref_frame_list, GstH264Picture *, i);
      if (pic->field == field) {
        pic = gst_h264_picture_ref (pic);
        g_array_append_val (ref_pic_list_x, pic);
        i++;
        break;
      }
    }

    for (; j < ref_frame_list->len; j++) {
      GstH264Picture *pic = g_array_index (ref_frame_list, GstH264Picture *, j);
      if (pic->field != field) {
        pic = gst_h264_picture_ref (pic);
        g_array_append_val (ref_pic_list_x, pic);
        j++;
        break;
      }
    }
  } while (i < ref_frame_list->len || j < ref_frame_list->len);
}

static void
construct_ref_field_pic_lists_p (GstH264Decoder * self,
    GstH264Picture * current_picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  gint pos;

  g_array_set_size (priv->ref_pic_list_p0, 0);
  g_array_set_size (priv->ref_frame_list_0_short_term, 0);
  g_array_set_size (priv->ref_frame_list_long_term, 0);

  /* 8.2.4.2.2, 8.2.4.2.5 refFrameList0ShortTerm:
   * short-term ref pictures sorted by descending frame_num_wrap.
   */
  gst_h264_dpb_get_pictures_short_term_ref (priv->dpb,
      TRUE, TRUE, priv->ref_frame_list_0_short_term);
  g_array_sort (priv->ref_frame_list_0_short_term,
      (GCompareFunc) frame_num_wrap_desc_compare);

#ifndef GST_DISABLE_GST_DEBUG
  if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_TRACE
      && priv->ref_frame_list_0_short_term->len) {
    GString *str = g_string_new (NULL);
    for (pos = 0; pos < priv->ref_frame_list_0_short_term->len; pos++) {
      GstH264Picture *ref = g_array_index (priv->ref_frame_list_0_short_term,
          GstH264Picture *, pos);
      g_string_append_printf (str, "|%i(%d)", ref->frame_num_wrap, ref->field);
    }
    GST_TRACE_OBJECT (self, "ref_frame_list_0_short_term (%d): %s|",
        current_picture->field, str->str);
    g_string_free (str, TRUE);
  }
#endif

  /* 8.2.4.2.2 refFrameList0LongTerm,:
   * long-term ref pictures sorted by ascending long_term_frame_idx.
   */
  gst_h264_dpb_get_pictures_long_term_ref (priv->dpb,
      TRUE, priv->ref_frame_list_long_term);
  g_array_sort (priv->ref_frame_list_long_term,
      (GCompareFunc) long_term_frame_idx_asc_compare);

#ifndef GST_DISABLE_GST_DEBUG
  if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_TRACE
      && priv->ref_frame_list_long_term->len) {
    GString *str = g_string_new (NULL);
    for (pos = 0; pos < priv->ref_frame_list_long_term->len; pos++) {
      GstH264Picture *ref = g_array_index (priv->ref_frame_list_0_short_term,
          GstH264Picture *, pos);
      g_string_append_printf (str, "|%i(%d)", ref->long_term_frame_idx,
          ref->field);
    }
    GST_TRACE_OBJECT (self, "ref_frame_list_0_long_term (%d): %s|",
        current_picture->field, str->str);
    g_string_free (str, TRUE);
  }
#endif

  /* 8.2.4.2.5 */
  init_picture_refs_fields_1 (self, current_picture->field,
      priv->ref_frame_list_0_short_term, priv->ref_pic_list_p0);
  init_picture_refs_fields_1 (self, current_picture->field,
      priv->ref_frame_list_long_term, priv->ref_pic_list_p0);

#ifndef GST_DISABLE_GST_DEBUG
  if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG
      && priv->ref_pic_list_p0->len) {
    GString *str = g_string_new (NULL);
    for (pos = 0; pos < priv->ref_pic_list_p0->len; pos++) {
      GstH264Picture *ref =
          g_array_index (priv->ref_pic_list_p0, GstH264Picture *, pos);
      if (!GST_H264_PICTURE_IS_LONG_TERM_REF (ref))
        g_string_append_printf (str, "|%i(%d)s", ref->frame_num_wrap,
            ref->field);
      else
        g_string_append_printf (str, "|%i(%d)l", ref->long_term_frame_idx,
            ref->field);
    }
    GST_DEBUG_OBJECT (self, "ref_pic_list_p0 (%d): %s|", current_picture->field,
        str->str);
    g_string_free (str, TRUE);
  }
#endif

  /* Clear temporary lists, now pictures are owned by ref_pic_list_p0 */
  g_array_set_size (priv->ref_frame_list_0_short_term, 0);
  g_array_set_size (priv->ref_frame_list_long_term, 0);
}

static gboolean
lists_are_equal (GArray * l1, GArray * l2)
{
  gint i;

  if (l1->len != l2->len)
    return FALSE;

  for (i = 0; i < l1->len; i++)
    if (g_array_index (l1, gpointer, i) != g_array_index (l2, gpointer, i))
      return FALSE;

  return TRUE;
}

static gint
split_ref_pic_list_b (GstH264Decoder * self, GArray * ref_pic_list_b,
    GCompareFunc compare_func)
{
  gint pos;

  for (pos = 0; pos < ref_pic_list_b->len; pos++) {
    GstH264Picture *pic = g_array_index (ref_pic_list_b, GstH264Picture *, pos);
    if (compare_func (&pic, &self->priv->current_picture) > 0)
      break;
  }

  return pos;
}

static void
print_ref_pic_list_b (GstH264Decoder * self, GArray * ref_list_b,
    const gchar * name)
{
#ifndef GST_DISABLE_GST_DEBUG
  GString *str;
  gint i;

  if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) < GST_LEVEL_DEBUG)
    return;

  str = g_string_new (NULL);

  for (i = 0; i < ref_list_b->len; i++) {
    GstH264Picture *ref = g_array_index (ref_list_b, GstH264Picture *, i);

    if (!GST_H264_PICTURE_IS_LONG_TERM_REF (ref))
      g_string_append_printf (str, "|%i", ref->pic_order_cnt);
    else
      g_string_append_printf (str, "|%il", ref->long_term_pic_num);
  }

  GST_DEBUG_OBJECT (self, "%s: %s| curr %i", name, str->str,
      self->priv->current_picture->pic_order_cnt);
  g_string_free (str, TRUE);
#endif
}

static void
construct_ref_pic_lists_b (GstH264Decoder * self,
    GstH264Picture * current_picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  gint pos;

  /* RefPicList0 (8.2.4.2.3) [[1] [2] [3]], where:
   * [1] shortterm ref pics with POC < current_picture's POC sorted by descending POC,
   * [2] shortterm ref pics with POC > current_picture's POC by ascending POC,
   * [3] longterm ref pics by ascending long_term_pic_num.
   */
  g_array_set_size (priv->ref_pic_list_b0, 0);
  g_array_set_size (priv->ref_pic_list_b1, 0);

  /* 8.2.4.2.3
   * When pic_order_cnt_type is equal to 0, reference pictures that are marked
   * as "non-existing" as specified in clause 8.2.5.2 are not included in either
   * RefPicList0 or RefPicList1
   */
  gst_h264_dpb_get_pictures_short_term_ref (priv->dpb,
      current_picture->pic_order_cnt_type != 0, FALSE, priv->ref_pic_list_b0);

  /* First sort ascending, this will put [1] in right place and finish
   * [2]. */
  print_ref_pic_list_b (self, priv->ref_pic_list_b0, "ref_pic_list_b0");
  g_array_sort (priv->ref_pic_list_b0, (GCompareFunc) poc_asc_compare);
  print_ref_pic_list_b (self, priv->ref_pic_list_b0, "ref_pic_list_b0");

  /* Find first with POC > current_picture's POC to get first element
   * in [2]... */
  pos = split_ref_pic_list_b (self, priv->ref_pic_list_b0,
      (GCompareFunc) poc_asc_compare);

  GST_DEBUG_OBJECT (self, "split point %i", pos);

  /* and sort [1] descending, thus finishing sequence [1] [2]. */
  g_qsort_with_data (priv->ref_pic_list_b0->data, pos, sizeof (gpointer),
      (GCompareDataFunc) poc_desc_compare, NULL);

  /* Now add [3] and sort by ascending long_term_pic_num. */
  pos = priv->ref_pic_list_b0->len;
  gst_h264_dpb_get_pictures_long_term_ref (priv->dpb,
      FALSE, priv->ref_pic_list_b0);
  g_qsort_with_data (&g_array_index (priv->ref_pic_list_b0, gpointer, pos),
      priv->ref_pic_list_b0->len - pos, sizeof (gpointer),
      (GCompareDataFunc) long_term_pic_num_asc_compare, NULL);

  /* RefPicList1 (8.2.4.2.4) [[1] [2] [3]], where:
   * [1] shortterm ref pics with POC > curr_pic's POC sorted by ascending POC,
   * [2] shortterm ref pics with POC < curr_pic's POC by descending POC,
   * [3] longterm ref pics by ascending long_term_pic_num.
   */
  gst_h264_dpb_get_pictures_short_term_ref (priv->dpb,
      current_picture->pic_order_cnt_type != 0, FALSE, priv->ref_pic_list_b1);

  /* First sort by descending POC. */
  g_array_sort (priv->ref_pic_list_b1, (GCompareFunc) poc_desc_compare);

  /* Split at first with POC < current_picture's POC to get first element
   * in [2]... */
  pos = split_ref_pic_list_b (self, priv->ref_pic_list_b1,
      (GCompareFunc) poc_desc_compare);

  /* and sort [1] ascending. */
  g_qsort_with_data (priv->ref_pic_list_b1->data, pos, sizeof (gpointer),
      (GCompareDataFunc) poc_asc_compare, NULL);

  /* Now add [3] and sort by ascending long_term_pic_num */
  pos = priv->ref_pic_list_b1->len;
  gst_h264_dpb_get_pictures_long_term_ref (priv->dpb,
      FALSE, priv->ref_pic_list_b1);
  g_qsort_with_data (&g_array_index (priv->ref_pic_list_b1, gpointer, pos),
      priv->ref_pic_list_b1->len - pos, sizeof (gpointer),
      (GCompareDataFunc) long_term_pic_num_asc_compare, NULL);

  /* If lists identical, swap first two entries in RefPicList1 (spec
   * 8.2.4.2.3) */
  if (priv->ref_pic_list_b1->len > 1
      && lists_are_equal (priv->ref_pic_list_b0, priv->ref_pic_list_b1)) {
    /* swap */
    GstH264Picture **list = (GstH264Picture **) priv->ref_pic_list_b1->data;
    GstH264Picture *pic = list[0];
    list[0] = list[1];
    list[1] = pic;
  }

  print_ref_pic_list_b (self, priv->ref_pic_list_b0, "ref_pic_list_b0");
  print_ref_pic_list_b (self, priv->ref_pic_list_b1, "ref_pic_list_b1");
}

static void
construct_ref_field_pic_lists_b (GstH264Decoder * self,
    GstH264Picture * current_picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  gint pos;

  /* refFrameList0ShortTerm (8.2.4.2.4) [[1] [2]], where:
   * [1] shortterm ref pics with POC < current_picture's POC sorted by descending POC,
   * [2] shortterm ref pics with POC > current_picture's POC by ascending POC,
   */
  g_array_set_size (priv->ref_pic_list_b0, 0);
  g_array_set_size (priv->ref_pic_list_b1, 0);
  g_array_set_size (priv->ref_frame_list_0_short_term, 0);
  g_array_set_size (priv->ref_frame_list_1_short_term, 0);
  g_array_set_size (priv->ref_frame_list_long_term, 0);

  /* 8.2.4.2.4
   * When pic_order_cnt_type is equal to 0, reference pictures that are marked
   * as "non-existing" as specified in clause 8.2.5.2 are not included in either
   * RefPicList0 or RefPicList1
   */
  gst_h264_dpb_get_pictures_short_term_ref (priv->dpb,
      current_picture->pic_order_cnt_type != 0, TRUE,
      priv->ref_frame_list_0_short_term);

  /* First sort ascending, this will put [1] in right place and finish
   * [2]. */
  print_ref_pic_list_b (self, priv->ref_frame_list_0_short_term,
      "ref_frame_list_0_short_term");
  g_array_sort (priv->ref_frame_list_0_short_term,
      (GCompareFunc) poc_asc_compare);
  print_ref_pic_list_b (self, priv->ref_frame_list_0_short_term,
      "ref_frame_list_0_short_term");

  /* Find first with POC > current_picture's POC to get first element
   * in [2]... */
  pos = split_ref_pic_list_b (self, priv->ref_frame_list_0_short_term,
      (GCompareFunc) poc_asc_compare);

  GST_DEBUG_OBJECT (self, "split point %i", pos);

  /* and sort [1] descending, thus finishing sequence [1] [2]. */
  g_qsort_with_data (priv->ref_frame_list_0_short_term->data, pos,
      sizeof (gpointer), (GCompareDataFunc) poc_desc_compare, NULL);

  /* refFrameList1ShortTerm (8.2.4.2.4) [[1] [2]], where:
   * [1] shortterm ref pics with POC > curr_pic's POC sorted by ascending POC,
   * [2] shortterm ref pics with POC < curr_pic's POC by descending POC,
   */
  gst_h264_dpb_get_pictures_short_term_ref (priv->dpb,
      current_picture->pic_order_cnt_type != 0, TRUE,
      priv->ref_frame_list_1_short_term);

  /* First sort by descending POC. */
  g_array_sort (priv->ref_frame_list_1_short_term,
      (GCompareFunc) poc_desc_compare);

  /* Split at first with POC < current_picture's POC to get first element
   * in [2]... */
  pos = split_ref_pic_list_b (self, priv->ref_frame_list_1_short_term,
      (GCompareFunc) poc_desc_compare);

  /* and sort [1] ascending. */
  g_qsort_with_data (priv->ref_frame_list_1_short_term->data, pos,
      sizeof (gpointer), (GCompareDataFunc) poc_asc_compare, NULL);

  /* 8.2.4.2.2 refFrameList0LongTerm,:
   * long-term ref pictures sorted by ascending long_term_frame_idx.
   */
  gst_h264_dpb_get_pictures_long_term_ref (priv->dpb,
      TRUE, priv->ref_frame_list_long_term);
  g_array_sort (priv->ref_frame_list_long_term,
      (GCompareFunc) long_term_frame_idx_asc_compare);

  /* 8.2.4.2.5 RefPicList0 */
  init_picture_refs_fields_1 (self, current_picture->field,
      priv->ref_frame_list_0_short_term, priv->ref_pic_list_b0);
  init_picture_refs_fields_1 (self, current_picture->field,
      priv->ref_frame_list_long_term, priv->ref_pic_list_b0);

  /* 8.2.4.2.5 RefPicList1 */
  init_picture_refs_fields_1 (self, current_picture->field,
      priv->ref_frame_list_1_short_term, priv->ref_pic_list_b1);
  init_picture_refs_fields_1 (self, current_picture->field,
      priv->ref_frame_list_long_term, priv->ref_pic_list_b1);

  /* If lists identical, swap first two entries in RefPicList1 (spec
   * 8.2.4.2.5) */
  if (priv->ref_pic_list_b1->len > 1
      && lists_are_equal (priv->ref_pic_list_b0, priv->ref_pic_list_b1)) {
    /* swap */
    GstH264Picture **list = (GstH264Picture **) priv->ref_pic_list_b1->data;
    GstH264Picture *pic = list[0];
    list[0] = list[1];
    list[1] = pic;
  }

  print_ref_pic_list_b (self, priv->ref_pic_list_b0, "ref_pic_list_b0");
  print_ref_pic_list_b (self, priv->ref_pic_list_b1, "ref_pic_list_b1");

  /* Clear temporary lists, now pictures are owned by ref_pic_list_b0
   * and ref_pic_list_b1 */
  g_array_set_size (priv->ref_frame_list_0_short_term, 0);
  g_array_set_size (priv->ref_frame_list_1_short_term, 0);
  g_array_set_size (priv->ref_frame_list_long_term, 0);
}

static void
gst_h264_decoder_prepare_ref_pic_lists (GstH264Decoder * self,
    GstH264Picture * current_picture)
{
  GstH264DecoderPrivate *priv = self->priv;
  gboolean construct_list = FALSE;
  gint i;
  GArray *dpb_array = gst_h264_dpb_get_pictures_all (priv->dpb);

  /* 8.2.4.2.1 ~ 8.2.4.2.4
   * When this process is invoked, there shall be at least one reference entry
   * that is currently marked as "used for reference"
   * (i.e., as "used for short-term reference" or "used for long-term reference")
   * and is not marked as "non-existing"
   */
  for (i = 0; i < dpb_array->len; i++) {
    GstH264Picture *picture = g_array_index (dpb_array, GstH264Picture *, i);
    if (GST_H264_PICTURE_IS_REF (picture) && !picture->nonexisting) {
      construct_list = TRUE;
      break;
    }
  }
  g_array_unref (dpb_array);

  if (!construct_list) {
    gst_h264_decoder_clear_ref_pic_lists (self);
    return;
  }

  if (GST_H264_PICTURE_IS_FRAME (current_picture)) {
    construct_ref_pic_lists_p (self, current_picture);
    construct_ref_pic_lists_b (self, current_picture);
  } else {
    construct_ref_field_pic_lists_p (self, current_picture);
    construct_ref_field_pic_lists_b (self, current_picture);
  }
}

static void
gst_h264_decoder_clear_ref_pic_lists (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv = self->priv;

  g_array_set_size (priv->ref_pic_list_p0, 0);
  g_array_set_size (priv->ref_pic_list_b0, 0);
  g_array_set_size (priv->ref_pic_list_b1, 0);
}

static gint
long_term_pic_num_f (GstH264Decoder * self, const GstH264Picture * picture)
{
  if (GST_H264_PICTURE_IS_LONG_TERM_REF (picture))
    return picture->long_term_pic_num;
  return 2 * (self->priv->max_long_term_frame_idx + 1);
}

static gint
pic_num_f (GstH264Decoder * self, const GstH264Picture * picture)
{
  if (!GST_H264_PICTURE_IS_LONG_TERM_REF (picture))
    return picture->pic_num;
  return self->priv->max_pic_num;
}

/* shift elements on the |array| starting from |from| to |to|,
 * inclusive, one position to the right and insert pic at |from| */
static void
shift_right_and_insert (GArray * array, gint from, gint to,
    GstH264Picture * picture)
{
  g_return_if_fail (from <= to);
  g_return_if_fail (array && picture);

  g_array_set_size (array, to + 2);
  g_array_insert_val (array, from, picture);
}

/* This can process either ref_pic_list0 or ref_pic_list1, depending
 * on the list argument. Set up pointers to proper list to be
 * processed here. */
static gboolean
modify_ref_pic_list (GstH264Decoder * self, int list)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264Picture *picture = priv->current_picture;
  GArray *ref_pic_listx;
  const GstH264SliceHdr *slice_hdr = &priv->current_slice.header;
  const GstH264RefPicListModification *list_mod;
  gboolean ref_pic_list_modification_flag_lX;
  gint num_ref_idx_lX_active_minus1;
  guint num_ref_pic_list_modifications;
  gint i;
  gint pic_num_lx_pred = picture->pic_num;
  gint ref_idx_lx = 0, src, dst;
  gint pic_num_lx_no_wrap;
  gint pic_num_lx;
  gboolean done = FALSE;
  GstH264Picture *pic;

  if (list == 0) {
    ref_pic_listx = priv->ref_pic_list0;
    ref_pic_list_modification_flag_lX =
        slice_hdr->ref_pic_list_modification_flag_l0;
    num_ref_pic_list_modifications = slice_hdr->n_ref_pic_list_modification_l0;
    num_ref_idx_lX_active_minus1 = slice_hdr->num_ref_idx_l0_active_minus1;
    list_mod = slice_hdr->ref_pic_list_modification_l0;
  } else {
    ref_pic_listx = priv->ref_pic_list1;
    ref_pic_list_modification_flag_lX =
        slice_hdr->ref_pic_list_modification_flag_l1;
    num_ref_pic_list_modifications = slice_hdr->n_ref_pic_list_modification_l1;
    num_ref_idx_lX_active_minus1 = slice_hdr->num_ref_idx_l1_active_minus1;
    list_mod = slice_hdr->ref_pic_list_modification_l1;
  }

  /* Resize the list to the size requested in the slice header.
   *
   * Note that per 8.2.4.2 it's possible for
   * num_ref_idx_lX_active_minus1 to indicate there should be more ref
   * pics on list than we constructed.  Those superfluous ones should
   * be treated as non-reference and will be initialized to null,
   * which must be handled by clients */
  g_assert (num_ref_idx_lX_active_minus1 >= 0);
  if (ref_pic_listx->len > num_ref_idx_lX_active_minus1 + 1)
    g_array_set_size (ref_pic_listx, num_ref_idx_lX_active_minus1 + 1);

  if (!ref_pic_list_modification_flag_lX)
    return TRUE;

  /* Spec 8.2.4.3:
   * Reorder pictures on the list in a way specified in the stream. */
  for (i = 0; i < num_ref_pic_list_modifications && !done; i++) {
    switch (list_mod->modification_of_pic_nums_idc) {
        /* 8.2.4.3.1 - Modify short reference picture position. */
      case 0:
      case 1:
        /* 8-34 */
        if (list_mod->modification_of_pic_nums_idc == 0) {
          /* Substract given value from predicted PicNum. */
          pic_num_lx_no_wrap = pic_num_lx_pred -
              (list_mod->value.abs_diff_pic_num_minus1 + 1);
          /* Wrap around max_pic_num if it becomes < 0 as result of
           * subtraction */
          if (pic_num_lx_no_wrap < 0)
            pic_num_lx_no_wrap += priv->max_pic_num;
        } else {                /* 8-35 */
          /* Add given value to predicted PicNum. */
          pic_num_lx_no_wrap = pic_num_lx_pred +
              (list_mod->value.abs_diff_pic_num_minus1 + 1);
          /* Wrap around max_pic_num if it becomes >= max_pic_num as
           * result of the addition */
          if (pic_num_lx_no_wrap >= priv->max_pic_num)
            pic_num_lx_no_wrap -= priv->max_pic_num;
        }

        /* For use in next iteration */
        pic_num_lx_pred = pic_num_lx_no_wrap;

        /* 8-36 */
        if (pic_num_lx_no_wrap > picture->pic_num)
          pic_num_lx = pic_num_lx_no_wrap - priv->max_pic_num;
        else
          pic_num_lx = pic_num_lx_no_wrap;

        /* 8-37 */
        g_assert (num_ref_idx_lX_active_minus1 + 1 < 32);
        pic = gst_h264_dpb_get_short_ref_by_pic_num (priv->dpb, pic_num_lx);
        if (!pic) {
          GST_WARNING_OBJECT (self, "Malformed stream, no pic num %d",
              pic_num_lx);
          break;
        }
        shift_right_and_insert (ref_pic_listx, ref_idx_lx,
            num_ref_idx_lX_active_minus1, pic);
        ref_idx_lx++;

        for (src = ref_idx_lx, dst = ref_idx_lx;
            src <= num_ref_idx_lX_active_minus1 + 1; src++) {
          GstH264Picture *src_pic =
              g_array_index (ref_pic_listx, GstH264Picture *, src);
          gint src_pic_num_lx = src_pic ? pic_num_f (self, src_pic) : -1;
          if (src_pic_num_lx != pic_num_lx)
            g_array_index (ref_pic_listx, GstH264Picture *, dst++) = src_pic;
        }

        break;

        /* 8.2.4.3.2 - Long-term reference pictures */
      case 2:
        /* (8-28) */
        g_assert (num_ref_idx_lX_active_minus1 + 1 < 32);
        pic = gst_h264_dpb_get_long_ref_by_long_term_pic_num (priv->dpb,
            list_mod->value.long_term_pic_num);
        if (!pic) {
          GST_WARNING_OBJECT (self, "Malformed stream, no pic num %d",
              list_mod->value.long_term_pic_num);
          break;
        }
        shift_right_and_insert (ref_pic_listx, ref_idx_lx,
            num_ref_idx_lX_active_minus1, pic);
        ref_idx_lx++;

        for (src = ref_idx_lx, dst = ref_idx_lx;
            src <= num_ref_idx_lX_active_minus1 + 1; src++) {
          GstH264Picture *src_pic =
              g_array_index (ref_pic_listx, GstH264Picture *, src);
          if (long_term_pic_num_f (self, src_pic) !=
              list_mod->value.long_term_pic_num)
            g_array_index (ref_pic_listx, GstH264Picture *, dst++) = src_pic;
        }

        break;

        /* End of modification list */
      case 3:
        done = TRUE;
        break;

      default:
        /* may be recoverable */
        GST_WARNING ("Invalid modification_of_pic_nums_idc = %d",
            list_mod->modification_of_pic_nums_idc);
        break;
    }

    list_mod++;
  }

  /* Per NOTE 2 in 8.2.4.3.2, the ref_pic_listx in the above loop is
   * temporarily made one element longer than the required final list.
   * Resize the list back to its required size. */
  if (ref_pic_listx->len > num_ref_idx_lX_active_minus1 + 1)
    g_array_set_size (ref_pic_listx, num_ref_idx_lX_active_minus1 + 1);

  return TRUE;
}

static void
copy_pic_list_into (GArray * dest, GArray * src)
{
  gint i;
  g_array_set_size (dest, 0);

  for (i = 0; i < src->len; i++)
    g_array_append_val (dest, g_array_index (src, gpointer, i));
}

static gboolean
gst_h264_decoder_modify_ref_pic_lists (GstH264Decoder * self)
{
  GstH264DecoderPrivate *priv = self->priv;
  GstH264SliceHdr *slice_hdr = &priv->current_slice.header;

  g_array_set_size (priv->ref_pic_list0, 0);
  g_array_set_size (priv->ref_pic_list1, 0);

  if (GST_H264_IS_P_SLICE (slice_hdr) || GST_H264_IS_SP_SLICE (slice_hdr)) {
    /* 8.2.4 fill reference picture list RefPicList0 for P or SP slice */
    copy_pic_list_into (priv->ref_pic_list0, priv->ref_pic_list_p0);
    return modify_ref_pic_list (self, 0);
  } else if (GST_H264_IS_B_SLICE (slice_hdr)) {
    /* 8.2.4 fill reference picture list RefPicList0 and RefPicList1 for B slice */
    copy_pic_list_into (priv->ref_pic_list0, priv->ref_pic_list_b0);
    copy_pic_list_into (priv->ref_pic_list1, priv->ref_pic_list_b1);
    return modify_ref_pic_list (self, 0)
        && modify_ref_pic_list (self, 1);
  }

  return TRUE;
}

/**
 * gst_h264_decoder_set_process_ref_pic_lists:
 * @decoder: a #GstH264Decoder
 * @process: whether subclass is requiring reference picture modification process
 *
 * Called to en/disable reference picture modification process.
 *
 * Since: 1.18
 */
void
gst_h264_decoder_set_process_ref_pic_lists (GstH264Decoder * decoder,
    gboolean process)
{
  decoder->priv->process_ref_pic_lists = process;
}

/**
 * gst_h264_decoder_get_picture:
 * @decoder: a #GstH264Decoder
 * @system_frame_number: a target system frame number of #GstH264Picture
 *
 * Retrive DPB and return a #GstH264Picture corresponding to
 * the @system_frame_number
 *
 * Returns: (transfer full) (nullable): a #GstH264Picture if successful, or %NULL otherwise
 *
 * Since: 1.18
 */
GstH264Picture *
gst_h264_decoder_get_picture (GstH264Decoder * decoder,
    guint32 system_frame_number)
{
  return gst_h264_dpb_get_picture (decoder->priv->dpb, system_frame_number);
}
