/* GStreamer
*
* Copyright (C) 2009 Carl-Anton Ingmarsson <ca.ingmarsson@gmail.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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/base/gstadapter.h>
#include <gst/base/gstbitreader.h>
#include <string.h>

#include "gsth264frame.h"

#include "gstvdph264dec.h"

GST_DEBUG_CATEGORY_STATIC (gst_vdp_h264_dec_debug);
#define GST_CAT_DEFAULT gst_vdp_h264_dec_debug

static GstStaticPadTemplate sink_template =
GST_STATIC_PAD_TEMPLATE (GST_BASE_VIDEO_DECODER_SINK_NAME,
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-h264, " "interlaced = (boolean) false")
    );

#define DEBUG_INIT(bla) \
    GST_DEBUG_CATEGORY_INIT (gst_vdp_h264_dec_debug, "vdpauh264dec", 0, \
    "VDPAU h264 decoder");

GST_BOILERPLATE_FULL (GstVdpH264Dec, gst_vdp_h264_dec, GstVdpDecoder,
    GST_TYPE_VDP_DECODER, DEBUG_INIT);

#define SYNC_CODE_SIZE 3

#define READ_UINT8(reader, val, nbits) { \
  if (!gst_bit_reader_get_bits_uint8 (reader, &val, nbits)) { \
    GST_WARNING ("failed to read uint8, nbits: %d", nbits); \
    return FALSE; \
  } \
}

#define READ_UINT16(reader, val, nbits) { \
  if (!gst_bit_reader_get_bits_uint16 (reader, &val, nbits)) { \
  GST_WARNING ("failed to read uint16, nbits: %d", nbits); \
    return FALSE; \
  } \
}

#define SKIP(reader, nbits) { \
  if (!gst_bit_reader_skip (reader, nbits)) { \
  GST_WARNING ("failed to skip nbits: %d", nbits); \
    return FALSE; \
  } \
}

static gboolean
gst_vdp_h264_dec_set_sink_caps (GstBaseVideoDecoder * base_video_decoder,
    GstCaps * caps)
{
  GstVdpH264Dec *h264_dec;
  GstStructure *structure;
  const GValue *value;

  h264_dec = GST_VDP_H264_DEC (base_video_decoder);

  structure = gst_caps_get_structure (caps, 0);
  /* packetized video has a codec_data */
  if ((value = gst_structure_get_value (structure, "codec_data"))) {
    GstBuffer *buf;
    GstBitReader reader;
    guint8 version;
    guint8 n_sps, n_pps;
    gint i;

    GST_DEBUG_OBJECT (h264_dec, "have packetized h264");
    h264_dec->packetized = TRUE;

    buf = gst_value_get_buffer (value);
    GST_MEMDUMP ("avcC:", GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));

    /* parse the avcC data */
    if (GST_BUFFER_SIZE (buf) < 7) {
      GST_ERROR_OBJECT (h264_dec, "avcC size %u < 7", GST_BUFFER_SIZE (buf));
      return FALSE;
    }

    gst_bit_reader_init_from_buffer (&reader, buf);

    READ_UINT8 (&reader, version, 8);
    if (version != 1)
      return FALSE;

    SKIP (&reader, 30);

    READ_UINT8 (&reader, h264_dec->nal_length_size, 2);
    h264_dec->nal_length_size += 1;
    GST_DEBUG_OBJECT (h264_dec, "nal length %u", h264_dec->nal_length_size);

    SKIP (&reader, 3);

    READ_UINT8 (&reader, n_sps, 5);
    for (i = 0; i < n_sps; i++) {
      guint16 sps_length;
      guint8 *data;

      READ_UINT16 (&reader, sps_length, 16);
      sps_length -= 1;
      SKIP (&reader, 8);

      data = GST_BUFFER_DATA (buf) + gst_bit_reader_get_pos (&reader) / 8;
      if (!gst_h264_parser_parse_sequence (h264_dec->parser, data, sps_length))
        return FALSE;

      SKIP (&reader, sps_length * 8);
    }

    READ_UINT8 (&reader, n_pps, 8);
    for (i = 0; i < n_pps; i++) {
      guint16 pps_length;
      guint8 *data;

      READ_UINT16 (&reader, pps_length, 16);
      pps_length -= 1;
      SKIP (&reader, 8);

      data = GST_BUFFER_DATA (buf) + gst_bit_reader_get_pos (&reader) / 8;
      if (!gst_h264_parser_parse_picture (h264_dec->parser, data, pps_length))
        return FALSE;

      SKIP (&reader, pps_length * 8);
    }
  }

  return TRUE;
}

static GstFlowReturn
gst_vdp_h264_dec_output (GstH264DPB * dpb, GstH264Frame * h264_frame,
    gpointer user_data)
{
  GstBaseVideoDecoder *base_video_decoder = (GstBaseVideoDecoder *) user_data;

  GST_DEBUG ("poc: %d", h264_frame->poc);

  return gst_base_video_decoder_finish_frame (base_video_decoder,
      GST_VIDEO_FRAME_CAST (h264_frame));
}

static guint
gst_vdp_h264_dec_calculate_poc (GstVdpH264Dec * h264_dec, GstH264Slice * slice)
{
  GstH264Picture *pic;
  GstH264Sequence *seq;
  guint poc = 0;

  pic = slice->picture;
  seq = pic->sequence;

  if (seq->pic_order_cnt_type == 0) {
    guint32 max_poc_cnt_lsb = 1 << (seq->log2_max_pic_order_cnt_lsb_minus4 + 4);

    if ((slice->pic_order_cnt_lsb < h264_dec->prev_poc_lsb) &&
        ((h264_dec->prev_poc_lsb - slice->pic_order_cnt_lsb) >=
            (max_poc_cnt_lsb / 2)))
      h264_dec->poc_msb = h264_dec->poc_msb + max_poc_cnt_lsb;

    else if ((slice->pic_order_cnt_lsb > h264_dec->prev_poc_lsb) &&
        ((slice->pic_order_cnt_lsb - h264_dec->prev_poc_lsb) >
            (max_poc_cnt_lsb / 2)))
      h264_dec->poc_msb = h264_dec->poc_msb - max_poc_cnt_lsb;

    poc = h264_dec->poc_msb + slice->pic_order_cnt_lsb;

    h264_dec->prev_poc_lsb = slice->pic_order_cnt_lsb;
  }

  return poc;
}

static void
gst_vdp_h264_dec_init_frame_info (GstVdpH264Dec * h264_dec,
    GstH264Frame * h264_frame)
{
  GstH264Slice *slice;

  slice = &h264_frame->slice_hdr;

  h264_frame->poc = gst_vdp_h264_dec_calculate_poc (h264_dec, slice);

  h264_frame->output_needed = TRUE;
  h264_frame->is_long_term = FALSE;
  h264_frame->frame_idx = slice->frame_num;

  /* is reference */
  if (slice->nal_unit.ref_idc == 0)
    h264_frame->is_reference = FALSE;
  else if (slice->nal_unit.IdrPicFlag) {
    h264_frame->is_reference = TRUE;
    if (slice->dec_ref_pic_marking.long_term_reference_flag) {
      h264_frame->is_long_term = TRUE;
      h264_frame->frame_idx = 0;
    }
  } else {
    h264_frame->is_reference = TRUE;

    if (slice->dec_ref_pic_marking.adaptive_ref_pic_marking_mode_flag) {
      GstH264RefPicMarking *marking;
      guint i;

      marking = slice->dec_ref_pic_marking.ref_pic_marking;
      for (i = 0; i < slice->dec_ref_pic_marking.n_ref_pic_marking; i++) {
        if (marking[i].memory_management_control_operation == 6) {
          h264_frame->is_long_term = TRUE;
          h264_frame->frame_idx = marking[i].long_term_frame_idx;
          break;
        }
      }
    }
  }

}

static gboolean
gst_vdp_h264_dec_calculate_par (GstH264VUIParameters * vui, guint16 * par_n,
    guint16 * par_d)
{
  guint16 aspect[16][2] = { {1, 1}, {12, 11}, {10, 11}, {16, 11}, {40, 33},
  {24, 11}, {20, 11}, {32, 11}, {80, 33}, {18, 11}, {15, 11}, {64, 33},
  {160, 99}, {4, 3}, {3, 2}, {2, 1}
  };

  if (vui->aspect_ratio_idc >= 1 && vui->aspect_ratio_idc <= 16) {
    *par_n = aspect[vui->aspect_ratio_idc - 1][0];
    *par_d = aspect[vui->aspect_ratio_idc - 1][1];
    return TRUE;
  } else if (vui->aspect_ratio_idc == 255) {
    *par_n = vui->sar_height;
    *par_d = vui->sar_width;
    return TRUE;
  }

  return FALSE;
}

static GstFlowReturn
gst_vdp_h264_dec_idr (GstVdpH264Dec * h264_dec, GstH264Frame * h264_frame)
{
  GstH264Slice *slice;
  GstH264Sequence *seq;

  h264_dec->poc_msb = 0;
  h264_dec->prev_poc_lsb = 0;

  slice = &h264_frame->slice_hdr;
  if (slice->dec_ref_pic_marking.no_output_of_prior_pics_flag)
    gst_h264_dpb_flush (h264_dec->dpb, FALSE);
  else
    gst_h264_dpb_flush (h264_dec->dpb, TRUE);

  if (slice->dec_ref_pic_marking.long_term_reference_flag)
    g_object_set (h264_dec->dpb, "max-longterm-frame-idx", 0, NULL);
  else
    g_object_set (h264_dec->dpb, "max-longterm-frame-idx", -1, NULL);

  seq = slice->picture->sequence;
  if (seq != h264_dec->sequence) {
    GstVideoState state;

    VdpDecoderProfile profile;
    GstFlowReturn ret;

    state =
        gst_base_video_decoder_get_state (GST_BASE_VIDEO_DECODER (h264_dec));

    state.width = (seq->pic_width_in_mbs_minus1 + 1) * 16 -
        2 * seq->frame_crop_right_offset;

    state.height = (2 - seq->frame_mbs_only_flag) *
        (seq->pic_height_in_map_units_minus1 + 1) * 16;
    if (seq->frame_mbs_only_flag)
      state.height -= 2 * seq->frame_crop_bottom_offset;
    else
      state.height -= 4 * seq->frame_crop_bottom_offset;

    /* calculate framerate if we haven't got one */
    if (state.fps_n == 0 && seq->vui_parameters_present_flag) {
      GstH264VUIParameters *vui;
      guint16 par_n, par_d;

      vui = &seq->vui_parameters;

      if (gst_vdp_h264_dec_calculate_par (vui, &par_n, &par_d)) {
        state.par_n = par_n;
        state.par_d = par_d;
      }

      if (vui->timing_info_present_flag && vui->fixed_frame_rate_flag) {
        state.fps_n = vui->time_scale;
        state.fps_d = vui->num_units_in_tick;

        if (seq->frame_mbs_only_flag)
          state.fps_d *= 2;
      }
    }

    gst_base_video_decoder_set_state (GST_BASE_VIDEO_DECODER (h264_dec), state);

    switch (seq->profile_idc) {
      case 66:
        profile = VDP_DECODER_PROFILE_H264_BASELINE;
        break;

      case 77:
        profile = VDP_DECODER_PROFILE_H264_MAIN;
        break;

      case 100:
        profile = VDP_DECODER_PROFILE_H264_HIGH;
        break;

      default:
        GST_ELEMENT_ERROR (h264_dec, STREAM, WRONG_TYPE,
            ("vdpauh264dec doesn't support this streams profile"),
            ("profile_idc: %d", seq->profile_idc));
        return GST_FLOW_ERROR;
    }

    ret = gst_vdp_decoder_init_decoder (GST_VDP_DECODER (h264_dec), profile,
        seq->num_ref_frames);
    if (ret != GST_FLOW_OK)
      return ret;

    g_object_set (h264_dec->dpb, "num-ref-frames", seq->num_ref_frames, NULL);

    h264_dec->sequence = seq;
  }

  return GST_FLOW_OK;
}

static VdpPictureInfoH264
gst_vdp_h264_dec_fill_info (GstVdpH264Dec * h264_dec, GstH264Frame * h264_frame)
{
  GstH264Slice *slice;
  GstH264Picture *pic;
  GstH264Sequence *seq;
  VdpPictureInfoH264 info;

  slice = &h264_frame->slice_hdr;
  pic = slice->picture;
  seq = pic->sequence;

  info.slice_count = h264_frame->slices->len;

  /* FIXME: we only handle frames for now */
  info.field_order_cnt[0] = h264_frame->poc;
  info.field_order_cnt[1] = h264_frame->poc;

  info.is_reference = h264_frame->is_reference;
  info.frame_num = slice->frame_num;

  info.field_pic_flag = slice->field_pic_flag;
  info.bottom_field_flag = slice->bottom_field_flag;
  info.num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1;
  info.num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1;

  info.num_ref_frames = seq->num_ref_frames;
  info.frame_mbs_only_flag = seq->frame_mbs_only_flag;
  info.mb_adaptive_frame_field_flag = seq->mb_adaptive_frame_field_flag;
  info.log2_max_frame_num_minus4 = seq->log2_max_frame_num_minus4;
  info.pic_order_cnt_type = seq->pic_order_cnt_type;
  info.log2_max_pic_order_cnt_lsb_minus4 =
      seq->log2_max_pic_order_cnt_lsb_minus4;
  info.delta_pic_order_always_zero_flag = seq->delta_pic_order_always_zero_flag;
  info.direct_8x8_inference_flag = seq->direct_8x8_inference_flag;


  info.constrained_intra_pred_flag = pic->constrained_intra_pred_flag;
  info.weighted_pred_flag = pic->weighted_pred_flag;
  info.weighted_bipred_idc = pic->weighted_bipred_idc;
  info.transform_8x8_mode_flag = pic->transform_8x8_mode_flag;
  info.chroma_qp_index_offset = pic->chroma_qp_index_offset;
  info.second_chroma_qp_index_offset = pic->second_chroma_qp_index_offset;
  info.pic_init_qp_minus26 = pic->pic_init_qp_minus26;
  info.entropy_coding_mode_flag = pic->entropy_coding_mode_flag;
  info.pic_order_present_flag = pic->pic_order_present_flag;
  info.deblocking_filter_control_present_flag =
      pic->deblocking_filter_control_present_flag;
  info.redundant_pic_cnt_present_flag = pic->redundant_pic_cnt_present_flag;

  memcpy (&info.scaling_lists_4x4, &pic->scaling_lists_4x4, 96);
  memcpy (&info.scaling_lists_8x8, &pic->scaling_lists_8x8, 128);

  gst_h264_dpb_fill_reference_frames (h264_dec->dpb, info.referenceFrames);

  return info;
}

static VdpBitstreamBuffer *
gst_vdp_h264_dec_create_bitstream_buffers (GstVdpH264Dec * h264_dec,
    GstH264Frame * h264_frame, guint * n_bufs)
{
  VdpBitstreamBuffer *bufs;

  if (h264_dec->packetized) {
    guint i;

    bufs = g_new (VdpBitstreamBuffer, h264_frame->slices->len * 2);
    *n_bufs = h264_frame->slices->len * 2;

    for (i = 0; i < h264_frame->slices->len; i++) {
      static const guint8 start_code[] = { 0x00, 0x00, 0x01 };
      guint idx;
      GstBuffer *buf;

      idx = i * 2;
      bufs[idx].bitstream = start_code;
      bufs[idx].bitstream_bytes = 3;
      bufs[idx].struct_version = VDP_BITSTREAM_BUFFER_VERSION;

      idx = idx + 1;
      buf = GST_BUFFER_CAST (g_ptr_array_index (h264_frame->slices, i));
      bufs[idx].bitstream = GST_BUFFER_DATA (buf) + h264_dec->nal_length_size;
      bufs[idx].bitstream_bytes = GST_BUFFER_SIZE (buf) -
          h264_dec->nal_length_size;
      bufs[idx].struct_version = VDP_BITSTREAM_BUFFER_VERSION;
    }
  }

  else {
    guint i;

    bufs = g_new (VdpBitstreamBuffer, h264_frame->slices->len);
    *n_bufs = h264_frame->slices->len;

    for (i = 0; i < h264_frame->slices->len; i++) {
      GstBuffer *buf;

      buf = GST_BUFFER_CAST (g_ptr_array_index (h264_frame->slices, i));
      bufs[i].bitstream = GST_BUFFER_DATA (buf);
      bufs[i].bitstream_bytes = GST_BUFFER_SIZE (buf);
      bufs[i].struct_version = VDP_BITSTREAM_BUFFER_VERSION;
    }
  }

  return bufs;
}

static GstFlowReturn
gst_vdp_h264_dec_handle_frame (GstBaseVideoDecoder * base_video_decoder,
    GstVideoFrame * frame, GstClockTimeDiff deadline)
{
  GstVdpH264Dec *h264_dec = GST_VDP_H264_DEC (base_video_decoder);

  GstH264Frame *h264_frame;
  GstH264Slice *slice;
  GstH264Picture *pic G_GNUC_UNUSED;
  GstH264Sequence *seq G_GNUC_UNUSED;

  GstFlowReturn ret;
  GstVdpVideoBuffer *outbuf;
  VdpPictureInfoH264 info;
  VdpBitstreamBuffer *bufs;
  guint n_bufs;

  GST_DEBUG ("handle_frame");

  h264_frame = GST_H264_FRAME_CAST (frame);

  slice = &h264_frame->slice_hdr;
  pic = slice->picture;
  seq = pic->sequence;


  if (slice->nal_unit.IdrPicFlag) {
    ret = gst_vdp_h264_dec_idr (h264_dec, h264_frame);

    if (ret == GST_FLOW_OK)
      h264_dec->got_idr = TRUE;
    else {
      gst_base_video_decoder_skip_frame (base_video_decoder, frame);
      return GST_FLOW_OK;
    }
  }

  /* check if we've got a IDR frame yet */
  if (!h264_dec->got_idr) {
    gst_base_video_decoder_skip_frame (base_video_decoder, frame);
    return GST_FLOW_OK;
  }


  gst_vdp_h264_dec_init_frame_info (h264_dec, h264_frame);

  info = gst_vdp_h264_dec_fill_info (h264_dec, h264_frame);
  bufs = gst_vdp_h264_dec_create_bitstream_buffers (h264_dec, h264_frame,
      &n_bufs);

  ret = gst_vdp_decoder_render (GST_VDP_DECODER (h264_dec),
      (VdpPictureInfo *) & info, n_bufs, bufs, &outbuf);
  g_free (bufs);

  if (ret != GST_FLOW_OK) {
    gst_base_video_decoder_skip_frame (base_video_decoder, frame);
    return ret;
  }

  frame->src_buffer = GST_BUFFER_CAST (outbuf);


  /* DPB handling */
  if (slice->nal_unit.ref_idc != 0 && !slice->nal_unit.IdrPicFlag) {
    if (slice->dec_ref_pic_marking.adaptive_ref_pic_marking_mode_flag) {
      GstH264RefPicMarking *marking;
      guint i;

      marking = slice->dec_ref_pic_marking.ref_pic_marking;
      for (i = 0; i < slice->dec_ref_pic_marking.n_ref_pic_marking; i++) {

        switch (marking[i].memory_management_control_operation) {
          case 1:
          {
            guint16 pic_num;

            pic_num = slice->frame_num -
                (marking[i].difference_of_pic_nums_minus1 + 1);
            gst_h264_dpb_mark_short_term_unused (h264_dec->dpb, pic_num);
            break;
          }

          case 2:
          {
            gst_h264_dpb_mark_long_term_unused (h264_dec->dpb,
                marking[i].long_term_pic_num);
            break;
          }

          case 3:
          {
            guint16 pic_num;

            pic_num = slice->frame_num -
                (marking[i].difference_of_pic_nums_minus1 + 1);
            gst_h264_dpb_mark_long_term (h264_dec->dpb, pic_num,
                marking[i].long_term_frame_idx);
            break;
          }

          case 4:
          {
            g_object_set (h264_dec->dpb, "max-longterm-frame-idx",
                marking[i].max_long_term_frame_idx_plus1 - 1, NULL);
            break;
          }

          case 5:
          {
            gst_h264_dpb_mark_all_unused (h264_dec->dpb);
            g_object_set (h264_dec->dpb, "max-longterm-frame-idx", -1, NULL);
            break;
          }

          default:
            break;
        }
      }
    } else
      gst_h264_dpb_mark_sliding (h264_dec->dpb);
  }

  return gst_h264_dpb_add (h264_dec->dpb, h264_frame);
}

static gint
gst_vdp_h264_dec_scan_for_sync (GstBaseVideoDecoder * base_video_decoder,
    GstAdapter * adapter)
{
  GstVdpH264Dec *h264_dec = GST_VDP_H264_DEC (base_video_decoder);
  gint m;

  if (h264_dec->packetized)
    return 0;

  m = gst_adapter_masked_scan_uint32 (adapter, 0xffffff00, 0x00000100,
      0, gst_adapter_available (adapter));
  if (m == -1)
    return gst_adapter_available (adapter) - SYNC_CODE_SIZE;

  return m;
}

static GstBaseVideoDecoderScanResult
gst_vdp_h264_dec_scan_for_packet_end (GstBaseVideoDecoder * base_video_decoder,
    GstAdapter * adapter, guint * size, gboolean at_eos)
{
  GstVdpH264Dec *h264_dec = GST_VDP_H264_DEC (base_video_decoder);
  guint avail;

  avail = gst_adapter_available (adapter);
  if (avail < h264_dec->nal_length_size)
    return GST_BASE_VIDEO_DECODER_SCAN_RESULT_NEED_DATA;

  if (h264_dec->packetized) {
    guint8 *data;
    gint i;
    guint32 nal_length = 0;

    data = g_slice_alloc (h264_dec->nal_length_size);
    gst_adapter_copy (adapter, data, 0, h264_dec->nal_length_size);
    for (i = 0; i < h264_dec->nal_length_size; i++)
      nal_length = (nal_length << 8) | data[i];

    g_slice_free1 (h264_dec->nal_length_size, data);

    nal_length += h264_dec->nal_length_size;

    /* check for invalid NALU sizes, assume the size if the available bytes
     * when something is fishy */
    if (nal_length <= 1 || nal_length > avail) {
      nal_length = avail - h264_dec->nal_length_size;
      GST_DEBUG ("fixing invalid NALU size to %u", nal_length);
    }

    *size = nal_length;
  }

  else {
    guint8 *data;
    guint32 start_code;
    guint n;

    data = g_slice_alloc (SYNC_CODE_SIZE);
    gst_adapter_copy (adapter, data, 0, SYNC_CODE_SIZE);
    start_code = ((data[0] << 16) && (data[1] << 8) && data[2]);
    g_slice_free1 (SYNC_CODE_SIZE, data);

    GST_DEBUG ("start_code: %d", start_code);
    if (start_code == 0x000001)
      return GST_BASE_VIDEO_DECODER_SCAN_RESULT_LOST_SYNC;

    n = gst_adapter_masked_scan_uint32 (adapter, 0xffffff00, 0x00000100,
        SYNC_CODE_SIZE, avail - SYNC_CODE_SIZE);
    if (n == -1)
      return GST_BASE_VIDEO_DECODER_SCAN_RESULT_NEED_DATA;

    *size = n;
  }

  GST_DEBUG ("NAL size: %d", *size);

  return GST_BASE_VIDEO_DECODER_SCAN_RESULT_OK;
}

static GstFlowReturn
gst_vdp_h264_dec_parse_data (GstBaseVideoDecoder * base_video_decoder,
    GstBuffer * buf, gboolean at_eos, GstVideoFrame * frame)
{
  GstVdpH264Dec *h264_dec = GST_VDP_H264_DEC (base_video_decoder);
  GstBitReader reader;
  GstNalUnit nal_unit;
  guint8 forbidden_zero_bit;

  guint8 *data;
  guint size;
  gint i;

  GstFlowReturn ret = GST_FLOW_OK;

  GST_MEMDUMP ("data", GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));

  gst_bit_reader_init_from_buffer (&reader, buf);

  if (gst_bit_reader_get_remaining (&reader) <
      h264_dec->nal_length_size * 8 + 7)
    goto invalid_packet;

  /* skip nal_length or sync code */
  gst_bit_reader_skip_unchecked (&reader, h264_dec->nal_length_size * 8);

  forbidden_zero_bit = gst_bit_reader_get_bits_uint8_unchecked (&reader, 1);

  if (forbidden_zero_bit != 0) {
    GST_WARNING ("forbidden_zero_bit != 0");
    return GST_FLOW_ERROR;
  }

  nal_unit.ref_idc = gst_bit_reader_get_bits_uint16_unchecked (&reader, 2);
  GST_DEBUG ("nal_ref_idc: %u", nal_unit.ref_idc);

  /* read nal_unit_type */
  nal_unit.type = gst_bit_reader_get_bits_uint16_unchecked (&reader, 5);

  GST_DEBUG ("nal_unit_type: %u", nal_unit.type);
  if (nal_unit.type == 14 || nal_unit.type == 20) {
    if (!gst_bit_reader_skip (&reader, 24))
      goto invalid_packet;
  }
  nal_unit.IdrPicFlag = (nal_unit.type == 5 ? 1 : 0);

  data = GST_BUFFER_DATA (buf) + gst_bit_reader_get_pos (&reader) / 8;
  size = gst_bit_reader_get_remaining (&reader) / 8;

  i = size - 1;
  while ((gint) size > 0 && data[i] == 0x00) {
    size--;
    i--;
  }

  if (GST_VIDEO_FRAME_FLAG_IS_SET (frame, GST_H264_FRAME_GOT_PRIMARY)) {
    if (nal_unit.type == GST_NAL_SPS || nal_unit.type == GST_NAL_PPS ||
        nal_unit.type == GST_NAL_SEI || nal_unit.type == GST_NAL_AU_DELIMITER ||
        (nal_unit.type >= 14 && nal_unit.type <= 18))
      ret =
          gst_base_video_decoder_have_frame (base_video_decoder, FALSE, &frame);
  }

  if (nal_unit.type >= GST_NAL_SLICE && nal_unit.type <= GST_NAL_SLICE_IDR) {
    GstH264Slice slice;

    if (!gst_h264_parser_parse_slice_header (h264_dec->parser, &slice, data,
            size, nal_unit))
      goto invalid_packet;

    if (slice.redundant_pic_cnt == 0) {
      if (GST_VIDEO_FRAME_FLAG_IS_SET (frame, GST_H264_FRAME_GOT_PRIMARY)) {
        GstH264Slice *p_slice;
        guint8 pic_order_cnt_type, p_pic_order_cnt_type;
        gboolean finish_frame = FALSE;

        p_slice = &(GST_H264_FRAME_CAST (frame)->slice_hdr);
        pic_order_cnt_type = slice.picture->sequence->pic_order_cnt_type;
        p_pic_order_cnt_type = p_slice->picture->sequence->pic_order_cnt_type;

        if (slice.frame_num != p_slice->frame_num)
          finish_frame = TRUE;

        else if (slice.picture != p_slice->picture)
          finish_frame = TRUE;

        else if (slice.bottom_field_flag != p_slice->bottom_field_flag)
          finish_frame = TRUE;

        else if (nal_unit.ref_idc != p_slice->nal_unit.ref_idc &&
            (nal_unit.ref_idc == 0 || p_slice->nal_unit.ref_idc == 0))
          finish_frame = TRUE;

        else if ((pic_order_cnt_type == 0 && p_pic_order_cnt_type == 0) &&
            (slice.pic_order_cnt_lsb != p_slice->pic_order_cnt_lsb ||
                slice.delta_pic_order_cnt_bottom !=
                p_slice->delta_pic_order_cnt_bottom))
          finish_frame = TRUE;

        else if ((p_pic_order_cnt_type == 1 && p_pic_order_cnt_type == 1) &&
            (slice.delta_pic_order_cnt[0] != p_slice->delta_pic_order_cnt[0] ||
                slice.delta_pic_order_cnt[1] !=
                p_slice->delta_pic_order_cnt[1]))
          finish_frame = TRUE;

        if (finish_frame)
          ret =
              gst_base_video_decoder_have_frame (base_video_decoder, FALSE,
              &frame);

      }

      if (!GST_VIDEO_FRAME_FLAG_IS_SET (frame, GST_H264_FRAME_GOT_PRIMARY)) {
        if (GST_H264_IS_I_SLICE (slice.type)
            || GST_H264_IS_SI_SLICE (slice.type))
          GST_VIDEO_FRAME_FLAG_SET (frame, GST_VIDEO_FRAME_FLAG_KEYFRAME);

        GST_H264_FRAME_CAST (frame)->slice_hdr = slice;
        GST_VIDEO_FRAME_FLAG_SET (frame, GST_H264_FRAME_GOT_PRIMARY);
      }
    }
    gst_h264_frame_add_slice ((GstH264Frame *) frame, buf);
  }

  if (nal_unit.type == GST_NAL_SPS) {
    if (!gst_h264_parser_parse_sequence (h264_dec->parser, data, size))
      goto invalid_packet;
  }

  if (nal_unit.type == GST_NAL_PPS) {
    if (!gst_h264_parser_parse_picture (h264_dec->parser, data, size))
      goto invalid_packet;
  }

  gst_buffer_unref (buf);
  return ret;

invalid_packet:
  GST_WARNING ("Invalid packet size!");
  gst_buffer_unref (buf);

  return GST_FLOW_OK;
}

static GstVideoFrame *
gst_vdp_h264_dec_create_frame (GstBaseVideoDecoder * base_video_decoder)
{
  return GST_VIDEO_FRAME_CAST (gst_h264_frame_new ());
}

static gboolean
gst_vdp_h264_dec_flush (GstBaseVideoDecoder * base_video_decoder)
{
  GstVdpH264Dec *h264_dec = GST_VDP_H264_DEC (base_video_decoder);

  h264_dec->got_idr = FALSE;
  gst_h264_dpb_flush (h264_dec->dpb, FALSE);

  return TRUE;
}

static gboolean
gst_vdp_h264_dec_start (GstBaseVideoDecoder * base_video_decoder)
{
  GstVdpH264Dec *h264_dec = GST_VDP_H264_DEC (base_video_decoder);

  h264_dec->packetized = FALSE;
  h264_dec->nal_length_size = SYNC_CODE_SIZE;

  h264_dec->got_idr = FALSE;
  h264_dec->sequence = NULL;

  h264_dec->parser = g_object_new (GST_TYPE_H264_PARSER, NULL);

  h264_dec->dpb = g_object_new (GST_TYPE_H264_DPB, NULL);
  gst_h264_dpb_set_output_func (h264_dec->dpb, gst_vdp_h264_dec_output,
      h264_dec);

  return GST_BASE_VIDEO_DECODER_CLASS
      (parent_class)->start (base_video_decoder);
}

static gboolean
gst_vdp_h264_dec_stop (GstBaseVideoDecoder * base_video_decoder)
{
  GstVdpH264Dec *h264_dec = GST_VDP_H264_DEC (base_video_decoder);

  g_object_unref (h264_dec->parser);
  g_object_unref (h264_dec->dpb);

  return GST_BASE_VIDEO_DECODER_CLASS (parent_class)->stop (base_video_decoder);
}

static void
gst_vdp_h264_dec_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_set_details_simple (element_class,
      "VDPAU H264 Decoder",
      "Decoder",
      "Decode h264 stream with vdpau",
      "Carl-Anton Ingmarsson <ca.ingmarsson@gmail.com>");

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&sink_template));
}

static void
gst_vdp_h264_dec_init (GstVdpH264Dec * h264_dec, GstVdpH264DecClass * klass)
{
}

static void
gst_vdp_h264_dec_class_init (GstVdpH264DecClass * klass)
{
  GstBaseVideoDecoderClass *base_video_decoder_class;

  base_video_decoder_class = GST_BASE_VIDEO_DECODER_CLASS (klass);

  base_video_decoder_class->start = gst_vdp_h264_dec_start;
  base_video_decoder_class->stop = gst_vdp_h264_dec_stop;
  base_video_decoder_class->flush = gst_vdp_h264_dec_flush;

  base_video_decoder_class->set_sink_caps = gst_vdp_h264_dec_set_sink_caps;

  base_video_decoder_class->scan_for_sync = gst_vdp_h264_dec_scan_for_sync;
  base_video_decoder_class->scan_for_packet_end =
      gst_vdp_h264_dec_scan_for_packet_end;
  base_video_decoder_class->parse_data = gst_vdp_h264_dec_parse_data;

  base_video_decoder_class->handle_frame = gst_vdp_h264_dec_handle_frame;
  base_video_decoder_class->create_frame = gst_vdp_h264_dec_create_frame;
}
