/*
 * Copyright (C) 2008-2020 the xine project
 * Copyright (C) 2008 Christophe Thommeret <hftom@free.fr>
 *
 * This file is part of xine, a free video player.
 *
 * xine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * xine 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 *
 * vdpau_vc1.c, a vc1 video stream parser using VDPAU hardware decoder
 *
 */

/*#define LOG*/
#define LOG_MODULE "vdpau_vc1"

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include <xine/xine_internal.h>
#include <xine/video_out.h>
#include <xine/buffer.h>
#include <xine/xineutils.h>
#include "accel_vdpau.h"
#include "bits_reader.h"
#include "group_vdpau.h"

#include <vdpau/vdpau.h>

#define sequence_header_code    0x0f
#define sequence_end_code       0x0a
#define entry_point_code        0x0e
#define frame_start_code        0x0d
#define field_start_code        0x0c
#define slice_start_code        0x0b

#define PICTURE_FRAME            0
#define PICTURE_FRAME_INTERLACE  2
#define PICTURE_FIELD_INTERLACE  3

#define I_FRAME   0
#define P_FRAME   1
#define B_FRAME   3
#define BI_FRAME  4

#define FIELDS_I_I    0
#define FIELDS_I_P    1
#define FIELDS_P_I    2
#define FIELDS_P_P    3
#define FIELDS_B_B    4
#define FIELDS_B_BI   5
#define FIELDS_BI_B   6
#define FIELDS_BI_BI  7

#define MODE_STARTCODE  0
#define MODE_FRAME      1

/*#define MAKE_DAT*/ /*do NOT define this, unless you know what you do */
#ifdef MAKE_DAT
static int nframes;
static FILE *outfile;
#endif



const double aspect_ratio[] = {
  0.0,
  1.0,
  12./11.,
  10./11.,
  16./11.,
  40./33.,
  24./11.,
  20./11.,
  32./11.,
  80./33.,
  18./11.,
  15./11.,
  64./33.,
  160./99.
};



typedef struct {
  VdpPictureInfoVC1       vdp_infos;
  int                     slices;
  int                     fptype;
  int                     field;
  int                     header_size;
  int                     hrd_param_flag;
  int                     hrd_num_leaky_buckets;
  int                     repeat_first_field;
  int                     top_field_first;
  int                     skipped;
} picture_t;



typedef struct {
  uint32_t    coded_width;
  uint32_t    coded_height;

  uint64_t    video_step; /* frame duration in pts units */
  uint64_t    reported_video_step; /* frame duration in pts units */
  double      ratio;
  VdpDecoderProfile profile;

  int         mode;
  int         have_header;

  uint8_t     *buf; /* accumulate data */
  int         bufseek;
  int         start;
  int         code_start, current_code;
  uint32_t    bufsize;
  uint32_t    bufpos;

  picture_t   picture;
  vo_frame_t  *forward_ref;
  vo_frame_t  *backward_ref;

  int64_t    seq_pts;
  int64_t    cur_pts;

  vdpau_accel_t *accel_vdpau;

  bits_reader_t br;

  int         vdp_runtime_nr;

  int         color_matrix;
} sequence_t;


typedef struct vdpau_vc1_decoder_s {
  video_decoder_t         video_decoder;  /* parent video decoder structure */

  xine_stream_t           *stream;

  sequence_t              sequence;

  VdpDecoder              decoder;
  VdpDecoderProfile       decoder_profile;
  uint32_t                decoder_width;
  uint32_t                decoder_height;

} vdpau_vc1_decoder_t;



static void init_picture( picture_t *pic )
{
  memset( pic, 0, sizeof( picture_t ) );
}



static void reset_picture( picture_t *pic )
{
  pic->slices = 1;
}



static void reset_sequence( sequence_t *sequence )
{
  lprintf( "reset_sequence\n" );
  sequence->bufpos = 0;
  sequence->bufseek = 0;
  sequence->start = -1;
  sequence->code_start = sequence->current_code = 0;
  sequence->seq_pts = sequence->cur_pts = 0;
  if ( sequence->forward_ref )
    sequence->forward_ref->free( sequence->forward_ref );
  sequence->forward_ref = NULL;
  if ( sequence->backward_ref )
    sequence->backward_ref->free( sequence->backward_ref );
  sequence->backward_ref = NULL;
  reset_picture( &sequence->picture );
}



static void init_sequence( sequence_t *sequence )
{
  lprintf( "init_sequence\n" );
  sequence->have_header = 0;
  sequence->profile = VDP_DECODER_PROFILE_VC1_SIMPLE;
  sequence->ratio = 0;
  sequence->video_step = 0;
  sequence->picture.hrd_param_flag = 0;
  reset_sequence( sequence );
}



static void update_metadata( vdpau_vc1_decoder_t *this_gen )
{
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;

  if ( !sequence->have_header ) {
    sequence->have_header = 1;
    _x_stream_info_set( this_gen->stream, XINE_STREAM_INFO_VIDEO_WIDTH, sequence->coded_width );
    _x_stream_info_set( this_gen->stream, XINE_STREAM_INFO_VIDEO_HEIGHT, sequence->coded_height );
    _x_stream_info_set( this_gen->stream, XINE_STREAM_INFO_VIDEO_RATIO, ((double)10000*sequence->ratio) );
    _x_stream_info_set( this_gen->stream, XINE_STREAM_INFO_FRAME_DURATION, (sequence->reported_video_step = sequence->video_step) );
    _x_meta_info_set_utf8( this_gen->stream, XINE_META_INFO_VIDEOCODEC, "VC1/WMV9 (vdpau)" );
    xine_event_t event;
    xine_format_change_data_t data;
    event.type = XINE_EVENT_FRAME_FORMAT_CHANGE;
    event.stream = this_gen->stream;
    event.data = &data;
    event.data_length = sizeof(data);
    data.width = sequence->coded_width;
    data.height = sequence->coded_height;
    data.aspect = sequence->ratio;
    xine_event_send( this_gen->stream, &event );
  }
}



static void sequence_header_advanced( vdpau_vc1_decoder_t *this_gen, uint8_t *buf, int len )
{
  lprintf( "sequence_header_advanced\n" );
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;
  int xine_color_matrix = 4; /* undefined, mpeg range */

  if ( len < 5 )
    return;

  sequence->profile = VDP_DECODER_PROFILE_VC1_ADVANCED;
  lprintf("VDP_DECODER_PROFILE_VC1_ADVANCED\n");
  bits_reader_set( &sequence->br, buf, len );
  skip_bits( &sequence->br, 15 );
  sequence->picture.vdp_infos.postprocflag = read_bits( &sequence->br, 1 );
  sequence->coded_width = (read_bits( &sequence->br, 12 )+1)<<1;
  sequence->coded_height = (read_bits( &sequence->br, 12 )+1)<<1;
  sequence->picture.vdp_infos.pulldown = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.interlace = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.tfcntrflag = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.finterpflag = read_bits( &sequence->br, 1 );
  skip_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.psf = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.maxbframes = 7;
  if ( read_bits( &sequence->br, 1 ) ) {
    double w, h;
    int ar=0;
    w = read_bits( &sequence->br, 14 )+1;
    h = read_bits( &sequence->br, 14 )+1;
    if ( read_bits( &sequence->br, 1 ) ) {
      ar = read_bits( &sequence->br, 4 );
    }
    if ( ar==15 ) {
      w = read_bits( &sequence->br, 8 );
      h = read_bits( &sequence->br, 8 );
      sequence->ratio = w/h;
      lprintf("aspect_ratio (w/h) = %f\n", sequence->ratio);
    }
    else if ( ar && ar<14 ) {
      sequence->ratio = sequence->coded_width*aspect_ratio[ar]/sequence->coded_height;
      lprintf("aspect_ratio = %f\n", sequence->ratio);
    }

    if ( read_bits( &sequence->br, 1 ) ) {
      if ( read_bits( &sequence->br, 1 ) ) {
#ifdef LOG
        int exp = read_bits( &sequence->br, 16 );
        lprintf("framerate exp = %d\n", exp);
#else
        skip_bits( &sequence->br, 16 );
#endif
      }
      else {
        double nr = read_bits( &sequence->br, 8 );
        switch ((int)nr) {
          case 1: nr = 24000; break;
          case 2: nr = 25000; break;
          case 3: nr = 30000; break;
          case 4: nr = 50000; break;
          case 5: nr = 60000; break;
          default: nr = 0;
        }
        double dr = read_bits( &sequence->br, 4 );
        switch ((int)dr) {
          case 2: dr = 1001; break;
          default: dr = 1000;
        }
        sequence->video_step = 90000/(nr/dr);
        lprintf("framerate = %f video_step = %" PRId64 "\n", nr/dr, sequence->video_step);
      }
    }
    if ( read_bits( &sequence->br, 1 ) ) {
      skip_bits( &sequence->br, 16 );
      xine_color_matrix = read_bits (&sequence->br, 8) << 1; /* VC1 is always mpeg range?? */
    }
  }
  VO_SET_FLAGS_CM (xine_color_matrix, sequence->color_matrix);
  sequence->picture.hrd_param_flag = read_bits( &sequence->br, 1 );
  if ( sequence->picture.hrd_param_flag )
    sequence->picture.hrd_num_leaky_buckets = read_bits( &sequence->br, 5 );

  update_metadata( this_gen );
}



static void sequence_header( vdpau_vc1_decoder_t *this_gen, uint8_t *buf, int len )
{
  lprintf( "sequence_header\n" );
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;

  if ( len < 4 )
    return;

  bits_reader_set( &sequence->br, buf, len );
  switch ( read_bits( &sequence->br, 2 ) ) {
    case 0: sequence->profile = VDP_DECODER_PROFILE_VC1_SIMPLE; lprintf("VDP_DECODER_PROFILE_VC1_SIMPLE\n"); break;
    case 1: sequence->profile = VDP_DECODER_PROFILE_VC1_MAIN; lprintf("VDP_DECODER_PROFILE_VC1_MAIN\n"); break;
    case 2: sequence->profile = VDP_DECODER_PROFILE_VC1_MAIN; fprintf(stderr, "vc1_complex profile not supported by vdpau, forcing vc1_main, expect corruption!.\n"); break;
    case 3: sequence_header_advanced( this_gen, buf, len ); return;
    default: return; /* illegal value, broken header? */
  }
  skip_bits( &sequence->br, 10 );
  sequence->picture.vdp_infos.loopfilter = read_bits( &sequence->br, 1 );
  skip_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.multires = read_bits( &sequence->br, 1 );
  skip_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.fastuvmc = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.extended_mv = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.dquant = read_bits( &sequence->br, 2 );
  sequence->picture.vdp_infos.vstransform = read_bits( &sequence->br, 1 );
  skip_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.overlap = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.syncmarker = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.rangered = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.maxbframes = read_bits( &sequence->br, 3 );
  sequence->picture.vdp_infos.quantizer = read_bits( &sequence->br, 2 );
  sequence->picture.vdp_infos.finterpflag = read_bits( &sequence->br, 1 );

  VO_SET_FLAGS_CM (4, sequence->color_matrix);

  update_metadata( this_gen );
}



static void entry_point( vdpau_vc1_decoder_t *this_gen, uint8_t *buf, int len )
{
  lprintf( "entry_point\n" );
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;

  bits_reader_set( &sequence->br, buf, len );
  skip_bits( &sequence->br, 2 );
  sequence->picture.vdp_infos.panscan_flag = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.refdist_flag = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.loopfilter = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.fastuvmc = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.extended_mv = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.dquant = read_bits( &sequence->br, 2 );
  sequence->picture.vdp_infos.vstransform = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.overlap = read_bits( &sequence->br, 1 );
  sequence->picture.vdp_infos.quantizer = read_bits( &sequence->br, 2 );

  if ( sequence->picture.hrd_param_flag ) {
    int i;
    for ( i=0; i<sequence->picture.hrd_num_leaky_buckets; ++i )
      skip_bits( &sequence->br, 8 );
  }

  if ( read_bits( &sequence->br, 1 ) ) {
    sequence->coded_width = (read_bits( &sequence->br, 12 )+1)<<1;
    sequence->coded_height = (read_bits( &sequence->br, 12 )+1)<<1;
  }

  if ( sequence->picture.vdp_infos.extended_mv )
    sequence->picture.vdp_infos.extended_dmv = read_bits( &sequence->br, 1 );

  sequence->picture.vdp_infos.range_mapy_flag = read_bits( &sequence->br, 1 );
  if ( sequence->picture.vdp_infos.range_mapy_flag ) {
    sequence->picture.vdp_infos.range_mapy = read_bits( &sequence->br, 3 );
  }
  sequence->picture.vdp_infos.range_mapuv_flag = read_bits( &sequence->br, 1 );
  if ( sequence->picture.vdp_infos.range_mapuv_flag ) {
    sequence->picture.vdp_infos.range_mapuv = read_bits( &sequence->br, 3 );
  }
}



static void picture_header( vdpau_vc1_decoder_t *this_gen, uint8_t *buf, int len )
{
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;
  //picture_t *pic = (picture_t*)&sequence->picture;
  VdpPictureInfoVC1 *info = &(sequence->picture.vdp_infos);
  int tmp;

  lprintf("picture_header\n");

  bits_reader_set( &sequence->br, buf, len );
  skip_bits( &sequence->br, 2 );

  if ( info->finterpflag )
    skip_bits( &sequence->br, 1 );
  if ( info->rangered ) {
    /*info->rangered &= ~2;
    info->rangered |= get_bits( buf,off++,1 ) << 1;*/
    info->rangered = (read_bits( &sequence->br, 1 ) << 1) +1;
  }
  if ( !info->maxbframes ) {
    if ( read_bits( &sequence->br, 1 ) )
      info->picture_type = P_FRAME;
    else
      info->picture_type = I_FRAME;
  }
  else {
    if ( read_bits( &sequence->br, 1 ) )
      info->picture_type = P_FRAME;
    else {
      if ( read_bits( &sequence->br, 1 ) )
        info->picture_type = I_FRAME;
      else
        info->picture_type = B_FRAME;
    }
  }
  if ( info->picture_type == B_FRAME ) {
    tmp = read_bits( &sequence->br, 3 );
    if ( tmp==7 ) {
      tmp = (tmp<<4) | read_bits( &sequence->br, 4 );
      if ( tmp==127 )
        info->picture_type = BI_FRAME;
    }
  }
}



static void picture_header_advanced( vdpau_vc1_decoder_t *this_gen, uint8_t *buf, int len )
{
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;
  picture_t *pic = (picture_t*)&sequence->picture;
  VdpPictureInfoVC1 *info = &(sequence->picture.vdp_infos);

  lprintf("picture_header_advanced\n");

  bits_reader_set( &sequence->br, buf, len );

  if ( info->interlace ) {
    lprintf("frame->interlace=1\n");
    if ( !read_bits( &sequence->br, 1 ) ) {
      lprintf("progressive frame\n");
      info->frame_coding_mode = PICTURE_FRAME;
    }
    else {
      if ( !read_bits( &sequence->br, 1 ) ) {
        lprintf("frame interlaced\n");
        info->frame_coding_mode = PICTURE_FRAME_INTERLACE;
      }
      else {
        lprintf("field interlaced\n");
        info->frame_coding_mode = PICTURE_FIELD_INTERLACE;
      }
    }
  }
  if ( info->interlace && info->frame_coding_mode == PICTURE_FIELD_INTERLACE ) {
    pic->fptype = read_bits( &sequence->br, 3 );
    switch ( pic->fptype ) {
      case FIELDS_I_I:
      case FIELDS_I_P:
        info->picture_type = I_FRAME; break;
      case FIELDS_P_I:
      case FIELDS_P_P:
        info->picture_type = P_FRAME; break;
      case FIELDS_B_B:
      case FIELDS_B_BI:
        info->picture_type = B_FRAME; break;
      default:
        info->picture_type = BI_FRAME;
    }
  }
  else {
    if ( !read_bits( &sequence->br, 1 ) )
      info->picture_type = P_FRAME;
    else {
      if ( !read_bits( &sequence->br, 1 ) )
        info->picture_type = B_FRAME;
      else {
        if ( !read_bits( &sequence->br, 1 ) )
          info->picture_type = I_FRAME;
        else {
          if ( !read_bits( &sequence->br, 1 ) )
            info->picture_type = BI_FRAME;
          else {
            info->picture_type = P_FRAME;
            pic->skipped = 1;
          }
        }
      }
    }
  }
  if ( info->tfcntrflag ) {
    lprintf("tfcntrflag=1\n");
    skip_bits( &sequence->br, 8 );
  }
  if ( info->pulldown && info->interlace ) {
    pic->top_field_first = read_bits( &sequence->br, 1 );
    pic->repeat_first_field = read_bits( &sequence->br, 1 );
  }
}



static void parse_header( vdpau_vc1_decoder_t *this_gen, uint8_t *buf, int len )
{
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;
  int off=0;

  while ( off < (len-4) ) {
    uint8_t *buffer = buf+off;
    if ( buffer[0]==0 && buffer[1]==0 && buffer[2]==1 ) {
      switch ( buffer[3] ) {
        case sequence_header_code: sequence_header( this_gen, buf+off+4, len-off-4 ); break;
        case entry_point_code: entry_point( this_gen, buf+off+4, len-off-4 ); break;
      }
    }
    ++off;
  }
  if ( !sequence->have_header )
    sequence_header( this_gen, buf, len );
}



static void remove_emulation_prevention( uint8_t *src, uint8_t *dst, int src_len, int *dst_len )
{
  int i;
  int len = 0;
  int removed = 0;

  for ( i=0; i<src_len-3; ++i ) {
    if ( src[i]==0 && src[i+1]==0 && src[i+2]==3 ) {
      lprintf("removed emulation prevention byte\n");
      dst[len++] = src[i];
      dst[len++] = src[i+1];
      i += 2;
      ++removed;
    }
    else {
      memcpy( dst+len, src+i, 4 );
      ++len;
    }
  }
  for ( ; i<src_len; ++i )
    dst[len++] = src[i];
  *dst_len = src_len-removed;
}



static int parse_code( vdpau_vc1_decoder_t *this_gen, uint8_t *buf, int len )
{
  sequence_t *sequence = (sequence_t*)&this_gen->sequence;

  if ( !sequence->have_header && buf[3]!=sequence_header_code )
    return 0;

  if ( sequence->code_start == frame_start_code ) {
    if ( sequence->current_code==field_start_code || sequence->current_code==slice_start_code ) {
	  sequence->picture.slices++;
      return -1;
	}
    return 1; /* frame complete, decode */
  }

  switch ( buf[3] ) {
    int dst_len;
    uint8_t *tmp;
    case sequence_header_code:
      lprintf("sequence_header_code\n");
      tmp = malloc( len );
      remove_emulation_prevention( buf, tmp, len, &dst_len );
      sequence_header( this_gen, tmp+4, dst_len-4 );
      free( tmp );
      break;
    case entry_point_code:
      lprintf("entry_point_code\n");
      tmp = malloc( len );
      remove_emulation_prevention( buf, tmp, len, &dst_len );
      entry_point( this_gen, tmp+4, dst_len-4 );
      free( tmp );
      break;
    case sequence_end_code:
      lprintf("sequence_end_code\n");
      break;
    case frame_start_code:
      lprintf("frame_start_code, len=%d\n", len);
      break;
    case field_start_code:
      lprintf("field_start_code\n");
      break;
    case slice_start_code:
      lprintf("slice_start_code, len=%d\n", len);
      break;
  }
  return 0;
}



static void decode_render( vdpau_vc1_decoder_t *vd, vdpau_accel_t *accel, uint8_t *buf, int len )
{
  sequence_t *seq = (sequence_t*)&vd->sequence;
  picture_t *pic = (picture_t*)&seq->picture;

  VdpStatus st;
  if ( vd->decoder==VDP_INVALID_HANDLE || vd->decoder_profile!=seq->profile || vd->decoder_width!=seq->coded_width || vd->decoder_height!=seq->coded_height ) {
    if (accel->lock)
      accel->lock (accel->vo_frame);
    if ( vd->decoder!=VDP_INVALID_HANDLE ) {
      accel->vdp_decoder_destroy( vd->decoder );
      vd->decoder = VDP_INVALID_HANDLE;
    }
    st = accel->vdp_decoder_create( accel->vdp_device, seq->profile, seq->coded_width, seq->coded_height, 2, &vd->decoder);
    if (accel->unlock)
      accel->unlock (accel->vo_frame);
    if ( st!=VDP_STATUS_OK )
      fprintf(stderr, "vdpau_vc1: failed to create decoder !! %s\n", accel->vdp_get_error_string( st ) );
    else {
      lprintf( "decoder created.\n" );
      vd->decoder_profile = seq->profile;
      vd->decoder_width = seq->coded_width;
      vd->decoder_height = seq->coded_height;
      seq->vdp_runtime_nr = accel->vdp_runtime_nr;
    }
  }

  VdpBitstreamBuffer vbit;
  vbit.struct_version = VDP_BITSTREAM_BUFFER_VERSION;
  vbit.bitstream = buf;
  vbit.bitstream_bytes = len;
  if ( pic->field )
    vbit.bitstream_bytes = pic->field;
  if (accel->lock)
    accel->lock (accel->vo_frame);
  st = accel->vdp_decoder_render( vd->decoder, accel->surface, CAST_VdpPictureInfo_PTR &pic->vdp_infos, 1, &vbit );
  if (accel->unlock)
    accel->unlock (accel->vo_frame);
  if ( st!=VDP_STATUS_OK )
    fprintf(stderr, "vdpau_vc1: decoder failed : %d!! %s\n", st, accel->vdp_get_error_string( st ) );
  else {
    lprintf( "DECODER SUCCESS : slices=%d, slices_bytes=%d, current=%d, forwref:%d, backref:%d, pts:%" PRId64 "\n",
              pic->vdp_infos.slice_count, vbit.bitstream_bytes, accel->surface, pic->vdp_infos.forward_reference, pic->vdp_infos.backward_reference, seq->seq_pts );
  }
#ifdef LOG
  VdpPictureInfoVC1 *info = &(seq->picture.vdp_infos);
  lprintf("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", info->slice_count, info->picture_type, info->frame_coding_mode,
           info->postprocflag, info->pulldown, info->interlace, info->tfcntrflag, info->finterpflag, info->psf, info->dquant, info->panscan_flag, info->refdist_flag,
           info->quantizer, info->extended_mv, info->extended_dmv, info->overlap, info->vstransform, info->loopfilter, info->fastuvmc, info->range_mapy_flag, info->range_mapy,
           info->range_mapuv_flag, info->range_mapuv, info->multires, info->syncmarker, info->rangered, info->maxbframes, info->deblockEnable, info->pquant );
#endif

  if ( pic->field ) {
    int old_type = pic->vdp_infos.picture_type;
    switch ( pic->fptype ) {
      case FIELDS_I_I:
      case FIELDS_P_I:
        pic->vdp_infos.picture_type = I_FRAME;
        pic->vdp_infos.backward_reference = VDP_INVALID_HANDLE;
        pic->vdp_infos.forward_reference = VDP_INVALID_HANDLE;
        break;
      case FIELDS_I_P:
        pic->vdp_infos.forward_reference = accel->surface;
        pic->vdp_infos.picture_type = P_FRAME;
        break;
      case FIELDS_P_P:
        if ( seq->backward_ref )
          pic->vdp_infos.forward_reference = ((vdpau_accel_t*)seq->backward_ref->accel_data)->surface;
        pic->vdp_infos.picture_type = P_FRAME;
        break;
      case FIELDS_B_B:
      case FIELDS_BI_B:
        pic->vdp_infos.picture_type = B_FRAME;
        break;
      default:
        pic->vdp_infos.picture_type = BI_FRAME;
    }
    vbit.bitstream = buf+pic->field+4;
    vbit.bitstream_bytes = len-pic->field-4;
    if (accel->lock)
      accel->lock (accel->vo_frame);
    st = accel->vdp_decoder_render( vd->decoder, accel->surface, CAST_VdpPictureInfo_PTR &pic->vdp_infos, 1, &vbit );
    if (accel->unlock)
      accel->unlock (accel->vo_frame);
    if ( st!=VDP_STATUS_OK )
      fprintf(stderr, "vdpau_vc1: decoder failed : %d!! %s\n", st, accel->vdp_get_error_string( st ) );
    else {
      lprintf( "DECODER SUCCESS (second field): slices=%d, slices_bytes=%d, current=%d, forwref:%d, backref:%d, pts:%" PRId64 "\n",
                pic->vdp_infos.slice_count, vbit.bitstream_bytes, accel->surface, pic->vdp_infos.forward_reference, pic->vdp_infos.backward_reference, seq->seq_pts );
    }
#ifdef LOG
    VdpPictureInfoVC1 *info = &(seq->picture.vdp_infos);
    lprintf("%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", info->slice_count, info->picture_type, info->frame_coding_mode,
             info->postprocflag, info->pulldown, info->interlace, info->tfcntrflag, info->finterpflag, info->psf, info->dquant, info->panscan_flag, info->refdist_flag,
             info->quantizer, info->extended_mv, info->extended_dmv, info->overlap, info->vstransform, info->loopfilter, info->fastuvmc, info->range_mapy_flag, info->range_mapy,
             info->range_mapuv_flag, info->range_mapuv, info->multires, info->syncmarker, info->rangered, info->maxbframes, info->deblockEnable, info->pquant );
#endif

    pic->vdp_infos.picture_type = old_type;
  }
}



static int search_field( vdpau_vc1_decoder_t *vd, uint8_t *buf, int len )
{
  int i;
  lprintf("search_fields, len=%d\n", len);
  (void)vd;
  for ( i=0; i<len-4; ++i ) {
    if ( buf[i]==0 && buf[i+1]==0 && buf[i+2]==1 && buf[i+3]==field_start_code ) {
      lprintf("found field_start_code at %d\n", i);
      return i;
    }
  }
  return 0;
}



static void decode_picture( vdpau_vc1_decoder_t *vd )
{
  sequence_t *seq = (sequence_t*)&vd->sequence;
  picture_t *pic = (picture_t*)&seq->picture;
  vdpau_accel_t *ref_accel;
  int field;

  uint8_t *buf;
  int len;

  pic->skipped = 0;
  pic->field = 0;

  if ( seq->mode == MODE_FRAME ) {
    buf = seq->buf;
    len = seq->bufpos;
    if ( seq->profile==VDP_DECODER_PROFILE_VC1_ADVANCED )
      picture_header_advanced( vd, buf, len );
    else
      picture_header( vd, buf, len );

    if ( len < 2 )
      pic->skipped = 1;
  }
  else {
    seq->picture.vdp_infos.slice_count = seq->picture.slices;
    buf = seq->buf+seq->start+4;
    len = seq->bufseek-seq->start-4;
    if ( seq->profile==VDP_DECODER_PROFILE_VC1_ADVANCED ) {
      int tmplen = (len>50) ? 50 : len;
      uint8_t *tmp = malloc( tmplen );
      remove_emulation_prevention( buf, tmp, tmplen, &tmplen );
      picture_header_advanced( vd, tmp, tmplen );
      free( tmp );
    }
    else
      picture_header( vd, buf, len );

    if ( len < 2 )
      pic->skipped = 1;
  }

  if ( pic->skipped )
    pic->vdp_infos.picture_type = P_FRAME;

  if ( pic->vdp_infos.interlace && pic->vdp_infos.frame_coding_mode == PICTURE_FIELD_INTERLACE ) {
    if ( !(field = search_field( vd, buf, len )) )
      lprintf("error, no fields found!\n");
    else
      pic->field = field;
  }

  pic->vdp_infos.forward_reference = VDP_INVALID_HANDLE;
  pic->vdp_infos.backward_reference = VDP_INVALID_HANDLE;

  if ( pic->vdp_infos.picture_type==P_FRAME ) {
    if ( seq->backward_ref ) {
      ref_accel = (vdpau_accel_t*)seq->backward_ref->accel_data;
      pic->vdp_infos.forward_reference = ref_accel->surface;
    }
    else {
      reset_picture( &seq->picture );
      return;
    }
  }
  else if ( pic->vdp_infos.picture_type>=B_FRAME ) {
    if ( seq->forward_ref ) {
      ref_accel = (vdpau_accel_t*)seq->forward_ref->accel_data;
      pic->vdp_infos.forward_reference = ref_accel->surface;
    }
    else {
      reset_picture( &seq->picture );
      return;
    }
    if ( seq->backward_ref ) {
      ref_accel = (vdpau_accel_t*)seq->backward_ref->accel_data;
      pic->vdp_infos.backward_reference = ref_accel->surface;
    }
    else {
      reset_picture( &seq->picture );
      return;
    } 
  }

  vo_frame_t *img = vd->stream->video_out->get_frame( vd->stream->video_out, seq->coded_width, seq->coded_height,
    seq->ratio, XINE_IMGFMT_VDPAU, VO_BOTH_FIELDS | seq->color_matrix );
  vdpau_accel_t *accel = (vdpau_accel_t*)img->accel_data;
  if ( !seq->accel_vdpau )
    seq->accel_vdpau = accel;

  if( seq->vdp_runtime_nr != *(seq->accel_vdpau->current_vdp_runtime_nr) ) {
    seq->accel_vdpau = accel;
    if ( seq->forward_ref )
      seq->forward_ref->free( seq->forward_ref );
    seq->forward_ref = NULL;
    if ( seq->backward_ref )
      seq->backward_ref->free( seq->backward_ref );
    seq->backward_ref = NULL;
    vd->decoder = VDP_INVALID_HANDLE;
  }

  decode_render( vd, accel, buf, len );


#ifdef MAKE_DAT
  if ( nframes==0 ) {
	fwrite( &seq->coded_width, 1, sizeof(seq->coded_width), outfile );
	fwrite( &seq->coded_height, 1, sizeof(seq->coded_height), outfile );
	fwrite( &seq->ratio, 1, sizeof(seq->ratio), outfile );
	fwrite( &seq->profile, 1, sizeof(seq->profile), outfile );
  }

  if ( nframes++ < 25 ) {
	fwrite( &pic->vdp_infos, 1, sizeof(pic->vdp_infos), outfile );
	fwrite( &len, 1, sizeof(len), outfile );
	fwrite( buf, 1, len, outfile );
	printf( "picture_type = %d\n", pic->vdp_infos.picture_type);
  }
#endif

  if ( pic->vdp_infos.interlace && pic->vdp_infos.frame_coding_mode ) {
    img->progressive_frame = 0;
    img->top_field_first = pic->top_field_first;
  }
  else {
    img->progressive_frame = 1;
    img->top_field_first = 1;
  }
  img->pts = seq->seq_pts;
  img->bad_frame = 0;
  img->duration = seq->video_step;

  if ( pic->vdp_infos.picture_type<B_FRAME ) {
    if ( pic->vdp_infos.picture_type==I_FRAME && !seq->backward_ref ) {
      img->pts = 0;
      img->draw( img, vd->stream );
      ++img->drawn;
    }
    if ( seq->forward_ref ) {
      seq->forward_ref->drawn = 0;
      seq->forward_ref->free( seq->forward_ref );
    }
    seq->forward_ref = seq->backward_ref;
    if ( seq->forward_ref && !seq->forward_ref->drawn ) {
      seq->forward_ref->draw( seq->forward_ref, vd->stream );
    }
    seq->backward_ref = img;
  }
  else {
    img->draw( img, vd->stream );
    img->free( img );
  }

  seq->seq_pts +=seq->video_step;

  reset_picture( &seq->picture );
}



/*
 * This function receives a buffer of data from the demuxer layer and
 * figures out how to handle it based on its header flags.
 */
static void vdpau_vc1_decode_data (video_decoder_t *this_gen, buf_element_t *buf)
{
  vdpau_vc1_decoder_t *this = (vdpau_vc1_decoder_t *) this_gen;
  sequence_t *seq = (sequence_t*)&this->sequence;

  /* a video decoder does not care about this flag (?) */
  if (buf->decoder_flags & BUF_FLAG_PREVIEW) {
    lprintf("BUF_FLAG_PREVIEW\n");
  }

  if (buf->decoder_flags & BUF_FLAG_FRAMERATE) {
    lprintf("BUF_FLAG_FRAMERATE=%d\n", buf->decoder_info[0]);
    if ( buf->decoder_info[0] > 0 ) {
      this->sequence.video_step = buf->decoder_info[0];
      _x_stream_info_set(this->stream, XINE_STREAM_INFO_FRAME_DURATION, this->sequence.video_step);
    }
  }
  
  if (this->sequence.reported_video_step != this->sequence.video_step){
    _x_stream_info_set(this->stream, XINE_STREAM_INFO_FRAME_DURATION, (this->sequence.reported_video_step = this->sequence.video_step));
  }

  if (buf->decoder_flags & BUF_FLAG_HEADER) {
    lprintf("BUF_FLAG_HEADER\n");
  }

  if (buf->decoder_flags & BUF_FLAG_ASPECT) {
    lprintf("BUF_FLAG_ASPECT\n");
    seq->ratio = (double)buf->decoder_info[1]/(double)buf->decoder_info[2];
    lprintf("arx=%d ary=%d ratio=%f\n", buf->decoder_info[1], buf->decoder_info[2], seq->ratio);
  }

  if ( !buf->size )
    return;

  seq->cur_pts = buf->pts;

  if (buf->decoder_flags & BUF_FLAG_STDHEADER) {
    lprintf("BUF_FLAG_STDHEADER\n");
    xine_bmiheader *bih = (xine_bmiheader *) buf->content;
    int bs = sizeof( xine_bmiheader );
    seq->coded_width = bih->biWidth;
    seq->coded_height = bih->biHeight;
    lprintf( "width=%d height=%d\n", bih->biWidth, bih->biHeight );
    if ( buf->size > bs ) {
      seq->mode = MODE_FRAME;
      parse_header( this, buf->content+bs, buf->size-bs );
    }
    return;
  }

  int size = seq->bufpos+buf->size;
  if ( seq->bufsize < (unsigned int)size ) {
    seq->bufsize = size+10000;
    seq->buf = realloc( seq->buf, seq->bufsize );
    lprintf("sequence buffer realloced = %d\n", seq->bufsize );
  }
  xine_fast_memcpy( seq->buf+seq->bufpos, buf->content, buf->size );
  seq->bufpos += buf->size;

  if (buf->decoder_flags & BUF_FLAG_FRAME_START) {
    lprintf("BUF_FLAG_FRAME_START\n");
    seq->seq_pts = buf->pts;
    seq->mode = MODE_FRAME;
    if ( seq->bufpos > 3 ) {
      if ( seq->buf[0]==0 && seq->buf[1]==0 && seq->buf[2]==1 ) {
        seq->mode = MODE_STARTCODE;
      }
    }
  }

  if ( seq->mode == MODE_FRAME ) {
    if ( buf->decoder_flags & BUF_FLAG_FRAME_END ) {
      lprintf("BUF_FLAG_FRAME_END\n");
      decode_picture( this );
      seq->bufpos = 0;
    }
    return;
  }

  int res;
  while ( seq->bufseek <= (int)(seq->bufpos)-4 ) {
    uint8_t *buffer = seq->buf+seq->bufseek;
    if ( buffer[0]==0 && buffer[1]==0 && buffer[2]==1 ) {
      seq->current_code = buffer[3];
      lprintf("current_code = %d\n", seq->current_code);
      if ( seq->start<0 ) {
        seq->start = seq->bufseek;
        seq->code_start = buffer[3];
        lprintf("code_start = %d\n", seq->code_start);
        if ( seq->cur_pts )
          seq->seq_pts = seq->cur_pts;
      }
      else {
        res = parse_code( this, seq->buf+seq->start, seq->bufseek-seq->start );
        if ( res==1 ) {
          seq->mode = MODE_STARTCODE;
          decode_picture( this );
          parse_code( this, seq->buf+seq->start, seq->bufseek-seq->start );
        }
        if ( res!=-1 ) {
          uint8_t *tmp = (uint8_t*)malloc(seq->bufsize);
          xine_fast_memcpy( tmp, seq->buf+seq->bufseek, seq->bufpos-seq->bufseek );
          seq->bufpos -= seq->bufseek;
          seq->start = -1;
          seq->bufseek = -1;
          free( seq->buf );
          seq->buf = tmp;
        }
      }
    }
    ++seq->bufseek;
  }
}



/*
 * This function is called when xine needs to flush the system.
 */
static void vdpau_vc1_flush (video_decoder_t *this_gen) {

  lprintf( "vdpau_vc1_flush\n" );
  (void)this_gen;
}

/*
 * This function resets the video decoder.
 */
static void vdpau_vc1_reset (video_decoder_t *this_gen) {
  vdpau_vc1_decoder_t *this = (vdpau_vc1_decoder_t *) this_gen;

  lprintf( "vdpau_vc1_reset\n" );
  reset_sequence( &this->sequence );
}

/*
 * The decoder should forget any stored pts values here.
 */
static void vdpau_vc1_discontinuity (video_decoder_t *this_gen) {

  lprintf( "vdpau_vc1_discontinuity\n" );
  (void)this_gen;
}

/*
 * This function frees the video decoder instance allocated to the decoder.
 */
static void vdpau_vc1_dispose (video_decoder_t *this_gen) {

  vdpau_vc1_decoder_t *this = (vdpau_vc1_decoder_t *) this_gen;

  lprintf( "vdpau_vc1_dispose\n" );

  if ( this->decoder!=VDP_INVALID_HANDLE && this->sequence.accel_vdpau ) {
      if (this->sequence.accel_vdpau->lock)
        this->sequence.accel_vdpau->lock (this->sequence.accel_vdpau->vo_frame);
      this->sequence.accel_vdpau->vdp_decoder_destroy( this->decoder );
      this->decoder = VDP_INVALID_HANDLE;
      if (this->sequence.accel_vdpau->unlock)
        this->sequence.accel_vdpau->unlock (this->sequence.accel_vdpau->vo_frame);
    }

  reset_sequence( &this->sequence );

  this->stream->video_out->close( this->stream->video_out, this->stream );

  free( this->sequence.buf );
  free( this_gen );
}

/*
 * This function allocates, initializes, and returns a private video
 * decoder structure.
 */
static video_decoder_t *open_plugin (video_decoder_class_t *class_gen, xine_stream_t *stream) {

  vdpau_vc1_decoder_t  *this ;

  lprintf( "open_plugin\n" );

  (void)class_gen;

  /* the videoout must be vdpau-capable to support this decoder */
  if ( !(stream->video_out->get_capabilities(stream->video_out) & VO_CAP_VDPAU_VC1) )
    return NULL;

  /* now check if vdpau has free decoder resource */
  vo_frame_t *img = stream->video_out->get_frame( stream->video_out, 1920, 1080, 1, XINE_IMGFMT_VDPAU,
                                                  VO_BOTH_FIELDS | VO_GET_FRAME_MAY_FAIL );
  if (!img) {
    return NULL;
  }
  vdpau_accel_t *accel = (vdpau_accel_t*)img->accel_data;
  int runtime_nr = accel->vdp_runtime_nr;
  img->free(img);
  VdpDecoder decoder;
  if (accel->lock)
    accel->lock (accel->vo_frame);
  VdpStatus st = accel->vdp_decoder_create( accel->vdp_device, VDP_DECODER_PROFILE_VC1_MAIN, 1920, 1080, 2, &decoder );
  if (accel->unlock)
    accel->unlock (accel->vo_frame);
  if ( st!=VDP_STATUS_OK ) {
    lprintf( "can't create vdpau decoder.\n" );
    return NULL;
  }

  if (accel->lock)
    accel->lock (accel->vo_frame);
  accel->vdp_decoder_destroy( decoder );
  if (accel->unlock)
    accel->unlock (accel->vo_frame);

  this = (vdpau_vc1_decoder_t *) calloc(1, sizeof(vdpau_vc1_decoder_t));
  if (!this)
    return NULL;

  this->video_decoder.decode_data         = vdpau_vc1_decode_data;
  this->video_decoder.flush               = vdpau_vc1_flush;
  this->video_decoder.reset               = vdpau_vc1_reset;
  this->video_decoder.discontinuity       = vdpau_vc1_discontinuity;
  this->video_decoder.dispose             = vdpau_vc1_dispose;

  this->stream                            = stream;

  this->sequence.bufsize = 10000;
  this->sequence.buf = (uint8_t*)malloc(this->sequence.bufsize);
  this->sequence.forward_ref = 0;
  this->sequence.backward_ref = 0;
  this->sequence.vdp_runtime_nr = runtime_nr;
  init_sequence( &this->sequence );

  init_picture( &this->sequence.picture );

  this->decoder = VDP_INVALID_HANDLE;
  this->sequence.accel_vdpau = NULL;
  this->sequence.mode = MODE_STARTCODE;

  (stream->video_out->open)(stream->video_out, stream);

#ifdef MAKE_DAT
  outfile = fopen( "/tmp/vc1.dat","w");
  nframes = 0;
#endif

  return &this->video_decoder;
}

/*
 * This function allocates a private video decoder class and initializes
 * the class's member functions.
 */
void *vc1_init_plugin (xine_t *xine, const void *data) {

  (void)xine;
  (void)data;

  static const video_decoder_class_t decode_video_vdpau_vc1_class = {
    .open_plugin     = open_plugin,
    .identifier      = "vdpau_vc1",
    .description     =
        N_("vdpau_vc1: vc1 decoder plugin using VDPAU hardware decoding.\n"
           "Must be used along with video_out_vdpau."),
    .dispose         = NULL,
  };

  return (void *)&decode_video_vdpau_vc1_class;
}


