/*
	2004.02.01
		first released source code for IOMP
*/
/* 
 * Copyright (C) 2000-2002 the xine project
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 * $Id: dxr3_decode_video.c,v 1.2 2003/11/25 04:25:46 georgedon Exp $
 */
 
/* dxr3 video decoder plugin.
 * Accepts the video data from xine and sends it directly to the
 * corresponding dxr3 device. Takes precedence over the libmpeg2
 * due to a higher priority.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include "xine_internal.h"
#include "buffer.h"
#include "video_out_dxr3.h"
#include "dxr3.h"

#define LOG_VID 0
#define LOG_PTS 0

/* the number of frames to pass after an out-of-sync situation
   before locking the stream again */
#define RESYNC_WINDOW_SIZE 50

/* we adjust vpts_offset in metronom, when skip_count reaches this value */
#define SKIP_TOLERANCE 200

/* the number of frames to pass before we stop duration correction */
#define FORCE_DURATION_WINDOW_SIZE 100


/* plugin class initialization function */
static void     *dxr3_init_plugin(xine_t *xine, void *);


/* plugin catalog information */
static uint32_t supported_types[] = { BUF_VIDEO_MPEG, 0 };

static decoder_info_t dxr3_video_decoder_info = {
  supported_types,     /* supported types */
  10                   /* priority        */
};

plugin_info_t xine_plugin_info[] = {
  /* type, API, "name", version, special_info, init_function */  
  { PLUGIN_VIDEO_DECODER, 15, "dxr3-mpeg2", XINE_VERSION_CODE, &dxr3_video_decoder_info, &dxr3_init_plugin },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};


/* plugin class functions */
static video_decoder_t *dxr3_open_plugin(video_decoder_class_t *class_gen, xine_stream_t *stream);
static char            *dxr3_get_identifier(video_decoder_class_t *class_gen);
static char            *dxr3_get_description(video_decoder_class_t *class_gen);
static void             dxr3_class_dispose(video_decoder_class_t *class_gen);

/* plugin instance functions */
static void dxr3_decode_data(video_decoder_t *this_gen, buf_element_t *buf);
static void dxr3_reset(video_decoder_t *this_gen);
static void dxr3_discontinuity(video_decoder_t *this_gen);
static void dxr3_flush(video_decoder_t *this_gen);
static void dxr3_dispose(video_decoder_t *this_gen);

/* plugin structures */
typedef struct dxr3_decoder_class_s {
  video_decoder_class_t  video_decoder_class;
  
  int                    instance;             /* we allow only one instance of this plugin */
  
  metronom_clock_t      *clock;                /* used for syncing */
} dxr3_decoder_class_t;

typedef struct dxr3_decoder_s {
  video_decoder_t        video_decoder;
  dxr3_decoder_class_t  *class;
  xine_stream_t         *stream;
  dxr3_scr_t            *scr;                  /* shortcut to the scr plugin in the dxr3 video out */
  
  char                   devname[128];
  char                   devnum[3];
  int                    fd_control;
  int                    fd_video;             /* to access the dxr3 devices */
  
  int                    have_header_info;
  int                    sequence_open;
  int                    width;
  int                    height;
  double                 ratio;
  int                    aspect_code;
  int                    frame_rate_code;
  int                    repeat_first_field;   /* mpeg stream header data */
  
  int                    force_aspect;         /* when input plugin has better info, we are forced */
  int                    force_pan_scan;       /* to use a certain aspect or to do pan&scan */
  
  int                    last_width;
  int                    last_height;
  int                    last_aspect_code;     /* used to detect changes for event sending */
  
  unsigned int           dts_offset[3];
  int                    sync_every_frame;
  int                    sync_retry;
  int                    enhanced_mode;
  int                    resync_window;
  int                    skip_count;           /* syncing parameters */
  
  int                    correct_durations;
  int64_t                last_vpts;
  int                    force_duration_window;
  int                    avg_duration;         /* logic to correct broken frame rates */
} dxr3_decoder_t;

/* helper functions */
static int       dxr3_present(xine_stream_t *stream);
static int       dxr3_mvcommand(int fd_control, int command);
static void      parse_mpeg_header(dxr3_decoder_t *this, uint8_t *buffer);
static int       get_duration(dxr3_decoder_t *this);

/* config callbacks */
static void      dxr3_update_sync_mode(void *this_gen, xine_cfg_entry_t *entry);
static void      dxr3_update_enhanced_mode(void *this_gen, xine_cfg_entry_t *entry);
static void      dxr3_update_correct_durations(void *this_gen, xine_cfg_entry_t *entry);


static void *dxr3_init_plugin(xine_t *xine, void *data)
{
  dxr3_decoder_class_t *this;
  
  this = (dxr3_decoder_class_t *)malloc(sizeof (dxr3_decoder_class_t));
  if (!this) return NULL;
  
  this->video_decoder_class.open_plugin     = dxr3_open_plugin;
  this->video_decoder_class.get_identifier  = dxr3_get_identifier;
  this->video_decoder_class.get_description = dxr3_get_description;
  this->video_decoder_class.dispose         = dxr3_class_dispose;
  
  this->instance                            = 0;
  
  this->clock                               = xine->clock;
  
  return &this->video_decoder_class;
}


static video_decoder_t *dxr3_open_plugin(video_decoder_class_t *class_gen, xine_stream_t *stream)
{
  dxr3_decoder_t *this;
  dxr3_decoder_class_t *class = (dxr3_decoder_class_t *)class_gen;
  config_values_t *cfg;
  const char *confstr;
  int dashpos;
  char tmpstr[128];
  
  if (class->instance) return NULL;
  if (!dxr3_present(stream)) return NULL;
  
  this = (dxr3_decoder_t *)malloc(sizeof (dxr3_decoder_t));
  if (!this) return NULL;
  
  cfg = stream->xine->config;
  
  this->video_decoder.decode_data   = dxr3_decode_data;
  this->video_decoder.reset         = dxr3_reset;
  this->video_decoder.discontinuity = dxr3_discontinuity;
  this->video_decoder.flush         = dxr3_flush;
  this->video_decoder.dispose       = dxr3_dispose;
  
  this->class                       = class;
  this->stream                      = stream;
  this->scr                         = NULL;
  
  confstr = cfg->register_string(cfg, CONF_LOOKUP, CONF_DEFAULT, CONF_NAME, CONF_HELP, 0, NULL, NULL);
  strncpy(this->devname, confstr, 128);
  this->devname[127] = '\0';
  dashpos = strlen(this->devname) - 2; /* the dash in the new device naming scheme would be here */
  if (this->devname[dashpos] == '-') {
    /* use new device naming scheme with trailing number */
    strncpy(this->devnum, &this->devname[dashpos], 3);
    this->devname[dashpos] = '\0';
  } else {
    /* use old device naming scheme without trailing number */
    /* FIXME: remove this when everyone uses em8300 >=0.12.0 */
    this->devnum[0] = '\0';
  }
  
  snprintf(tmpstr, sizeof(tmpstr), "%s%s", this->devname, this->devnum);
#if LOG_VID
  printf("dxr3_decode_video: Entering video init, devname=%s.\n",tmpstr);
#endif
  
  /* open later, because dxr3_video_out might have it open until we request a frame */
  this->fd_video = -1;
  
  if ((this->fd_control = open(tmpstr, O_WRONLY)) < 0) {
    printf("dxr3_decode_video: Failed to open control device %s (%s)\n",
      tmpstr, strerror(errno));
    free(this);
    return NULL;
  }
  
  this->have_header_info      = 0;
  this->sequence_open         = 0;
  this->repeat_first_field    = 0;
  
  this->force_aspect          = 0;
  this->force_pan_scan        = 0;
  
  this->last_width            = 0;
  this->last_height           = 0;
  this->last_aspect_code      = 0;
  
  this->dts_offset[0]         = 21600;
  this->dts_offset[1]         = 21600;
  this->dts_offset[2]         = 21600;
  this->sync_retry            = 0;
  this->resync_window         = 0;
  this->skip_count            = 0;
  
  this->force_duration_window = -FORCE_DURATION_WINDOW_SIZE;
  this->last_vpts             = this->class->clock->get_current_time(this->class->clock);
  this->avg_duration          = 0;
  
  this->sync_every_frame      = cfg->register_bool(cfg,
    "dxr3.sync_every_frame", 0, _("Try to sync video every frame"),
    _("This is relevant for progressive video only (most PAL films)."), 20,
    dxr3_update_sync_mode, this);
  this->enhanced_mode         = cfg->register_bool(cfg,
    "dxr3.alt_play_mode", 1, _("Use alternate Play mode"),
    _("Enabling this option will utilise a smoother play mode."), 10,
    dxr3_update_enhanced_mode, this);
  this->correct_durations     = cfg->register_bool(cfg,
    "dxr3.correct_durations", 0, _("Correct frame durations in broken streams"),
    _("Enable this for streams with wrong frame durations."), 10,
    dxr3_update_correct_durations, this);
  
  /* the dxr3 needs a longer prebuffering to have time for its internal decoding */
  this->stream->metronom_prebuffer = 90000;
  
  stream->video_out->open(stream->video_out, stream);
  
  class->instance = 1;
  
  return &this->video_decoder;
}

static char *dxr3_get_identifier(video_decoder_class_t *class_gen)
{
  return "dxr3-mpeg2";
}

static char *dxr3_get_description(video_decoder_class_t *class_gen)
{
  return "MPEGI/II decoder plugin using the hardware decoding capabilities of a DXR3 decoder card.";
}

static void dxr3_class_dispose(video_decoder_class_t *class_gen)
{
  free(class_gen);
}
 

static void dxr3_decode_data(video_decoder_t *this_gen, buf_element_t *buf)
{
  dxr3_decoder_t *this = (dxr3_decoder_t *)this_gen;
  ssize_t written;
  int64_t vpts;
  int i, skip;
  vo_frame_t *img;
  uint8_t *buffer, byte;
  uint32_t shift;
    
  vpts = 0;
  
  /* handle aspect hints from xine-dvdnav */
  if (buf->decoder_flags & BUF_FLAG_SPECIAL) {
    if (buf->decoder_info[1] == BUF_SPECIAL_ASPECT) {
      xine_event_t event;
      xine_format_change_data_t data;
      
      this->aspect_code = this->force_aspect = buf->decoder_info[2];
      if (buf->decoder_info[3] == 0x1 && this->force_aspect == 3)
	/* letterboxing is denied, we have to do pan&scan */
	this->force_pan_scan = 1;
      else
	this->force_pan_scan = 0;

      /* send an event for dxr3 spu decoder */
      event.type        = XINE_EVENT_FRAME_FORMAT_CHANGE;
      event.stream      = this->stream;
      event.data        = &data;
      event.data_length = sizeof(data);
      data.width        = this->last_width;
      data.height       = this->last_height;
      data.aspect       = this->force_aspect;
      data.pan_scan     = this->force_pan_scan;
      xine_event_send(this->stream, &event);
      
      /* update ratio */
      switch (this->aspect_code) {
      case 2:
	this->ratio = 4.0 / 3.0;
	break;
      case 3:
	this->ratio = 16.0 / 9.0;
	break;
      case 4:
	this->ratio = 2.11;
	break;
      default:
	if (this->have_header_info)
	  this->ratio = (double)this->last_width / (double)this->last_height;
      }
      
      this->last_aspect_code = this->aspect_code;
    }
    return;
  }
  
  /* parse frames in the buffer handed in, evaluate headers,
   * send frames to video_out and handle some syncing
   */
  buffer = buf->content;
  shift = 0xffffff00;
  for (i = 0; i < buf->size; i++) {
    byte = *buffer++;
    if (shift != 0x00000100) {
      shift = (shift | byte) << 8;
      continue;
    }
    /* header code of some kind found */
    shift = 0xffffff00;
    if (byte == 0xb3) {
      /* sequence data */
      if (buffer + 3 < buf->content + buf->size)
        parse_mpeg_header(this, buffer);
      this->sequence_open = 1;
      continue;
    }
    if (byte == 0xb5) {
      /* extension data */
      if (buffer + 3 < buf->content + buf->size)
	if ((buffer[0] & 0xf0) == 0x80)
	  this->repeat_first_field = (buffer[3] >> 1) & 1;
#if 0
      /* this disables frame jitter in progressive content, but
       * unfortunately it makes the card drop one field on stills */
      if (buffer + 4 < buf->content + buf->size)
	if ((buffer[0] & 0xf0) == 0x80)
	  buffer[4] &= ~(1 << 7);
#endif
      /* check if we can keep syncing */
      if (this->repeat_first_field && this->sync_retry)  /* reset counter */
        this->sync_retry = 500;
      if (this->repeat_first_field && this->sync_every_frame) {
#if LOG_VID
        printf("dxr3: non-progressive video detected. "
          "disabling sync_every_frame.\n");
#endif
        this->sync_every_frame = 0;
        this->sync_retry = 500; /* see you later */
      }
      continue;
    }
    if (byte == 0xb7)
      /* sequence end */
      this->sequence_open = 0;
    if (byte != 0x00)  /* Don't care what it is. It's not a new frame */
      continue;
    /* we have a code for a new frame */
    if (!this->have_header_info)  /* this->width et al may still be undefined */
      continue;
    if (buf->decoder_flags & BUF_FLAG_PREVIEW)
      continue;
    
    /* pretend like we have decoded a frame */
    img = this->stream->video_out->get_frame(this->stream->video_out,
      this->width, this->height, this->ratio,
      XINE_IMGFMT_DXR3, VO_BOTH_FIELDS | (this->force_pan_scan ? VO_PAN_SCAN_FLAG : 0));
    img->pts       = buf->pts;
    img->bad_frame = 0;
    img->duration  = get_duration(this);
    
    skip = img->draw(img, this->stream);
    
    if (skip <= 0) { /* don't skip */
      vpts = img->vpts; /* copy so we can free img */
      
      if (this->correct_durations) {
        /* calculate an average frame duration from metronom's vpts values */
        this->avg_duration = this->avg_duration * 0.9 + (vpts - this->last_vpts) * 0.1;
#if LOG_PTS
        printf("dxr3_decode_video: average frame duration %d\n", this->avg_duration);
#endif
      }
      
      if (this->skip_count) this->skip_count--;
      
      if (this->resync_window == 0 && this->scr && this->enhanced_mode &&
	  !this->scr->scanning) {
        /* we are in sync, so we can lock the stream now */
#if LOG_VID
        printf("dxr3_decode_video: in sync, stream locked\n");
#endif
        dxr3_mvcommand(this->fd_control, MVCOMMAND_SYNC);
        this->resync_window = -RESYNC_WINDOW_SIZE;
      }
      if (this->resync_window != 0 && this->resync_window > -RESYNC_WINDOW_SIZE)
        this->resync_window--;
    } else { /* metronom says skip, so don't set vpts */
#if LOG_VID
      printf("dxr3_decode_video: %d frames to skip\n", skip);
#endif
      vpts = 0;
      this->avg_duration = 0;
      
      /* handle frame skip conditions */
      if (this->scr && !this->scr->scanning) this->skip_count += skip;
      if (this->skip_count > SKIP_TOLERANCE) {
        /* we have had enough skipping messages now, let's react */
        int64_t vpts_adjust = skip * (int64_t)img->duration / 2;
        if (vpts_adjust > 90000) vpts_adjust = 90000;
        this->stream->metronom->set_option(this->stream->metronom,
          METRONOM_ADJ_VPTS_OFFSET, vpts_adjust);
        this->skip_count = 0;
        this->resync_window = 0;
      }
      
      if (this->scr && this->scr->scanning) this->resync_window = 0;
      if (this->resync_window == 0 && this->scr && this->enhanced_mode &&
	  !this->scr->scanning) {
        /* switch off sync mode in the card to allow resyncing */
#if LOG_VID
        printf("dxr3_decode_video: out of sync, allowing stream resync\n");
#endif
        dxr3_mvcommand(this->fd_control, MVCOMMAND_START);
        this->resync_window = RESYNC_WINDOW_SIZE;
      }
      if (this->resync_window != 0 && this->resync_window < RESYNC_WINDOW_SIZE)
        this->resync_window++;
    }
    this->last_vpts = img->vpts;
    img->free(img);

    /* if sync_every_frame was disabled, decrease the counter
     * for a retry 
     * (it might be due to crappy studio logos and stuff
     * so we should give the main movie a chance)
     */
    if (this->sync_retry) {
      if (!--this->sync_retry) {
#if LOG_VID
        printf("dxr3_decode_video: retrying sync_every_frame");
#endif
        this->sync_every_frame = 1;
      }
    }
  }
  if (buf->decoder_flags & BUF_FLAG_PREVIEW) return;
  
  /* ensure video device is open
   * (we open it late because on occasion the dxr3 video out driver
   * wants to open it)
   * also ensure the scr is running
   */
  if (this->fd_video < 0) {
    metronom_clock_t *clock = this->class->clock;
    char tmpstr[128];
    int64_t time;
    
    /* open the device for the decoder */
    snprintf (tmpstr, sizeof(tmpstr), "%s_mv%s", this->devname, this->devnum);
    if ((this->fd_video = open(tmpstr, O_WRONLY | O_NONBLOCK)) < 0) {
      printf("dxr3_decode_video: Failed to open video device %s (%s)\n",
        tmpstr, strerror(errno)); 
      return;
    }
    
    /* We may want to issue a SETPTS, so make sure the scr plugin
     * is running and registered. Unfortuantely wa cannot do this
     * earlier, because the dxr3's internal scr gets confused
     * when started with a closed video device. Maybe this is a
     * driver bug and gets fixed somewhen. FIXME: We might then
     * want to do this entirely in the video out.
     */
    this->scr = ((dxr3_driver_t *)this->stream->video_driver)->class->scr;
    time = clock->get_current_time(clock);
    this->scr->scr_plugin.start(&this->scr->scr_plugin, time);
    clock->register_scr(clock, &this->scr->scr_plugin);
#if LOG_VID
    if (this->class->clock->scr_master == &this->scr->scr_plugin)
      printf("dxr3_decode_video: dxr3_scr plugin is master\n");
    else
      printf("dxr3_decode_video: dxr3scr plugin is NOT master\n");
#endif
  }
  
  /* update the pts timestamp in the card, which tags the data we write to it */
  if (vpts) {
    int64_t delay;
    
    /* The PTS values written to the DXR3 must be modified based on the difference
     * between stream's PTS and DTS (decoder timestamp). We receive this
     * difference via decoder_info */
    buf->decoder_info[0] <<= 1;
    if (buf->pts) {
      if ((this->dts_offset[0] == buf->decoder_info[0]) &&
	  (this->dts_offset[1] == buf->decoder_info[0]))
	this->dts_offset[2] = buf->decoder_info[0];
      else {
	this->dts_offset[1] = this->dts_offset[0];
	this->dts_offset[0] = buf->decoder_info[0];
      }
#if LOG_PTS
      printf("dxr3_decode_video: PTS to DTS correction: %d\n", this->dts_offset[1]);
#endif
    }
    vpts -= this->dts_offset[2];
    
    delay = vpts - this->class->clock->get_current_time(
      this->class->clock);
#if LOG_PTS
    printf("dxr3_decode_video: SETPTS got %lld\n", vpts);
#endif
    /* SETPTS only if less then one second in the future and
     * either buffer has pts or sync_every_frame is set */
    if ((delay > 0) && (delay < 90000) &&
      (this->sync_every_frame || buf->pts)) {
      uint32_t vpts32 = vpts;
      /* update the dxr3's current pts value */
      if (ioctl(this->fd_video, EM8300_IOCTL_VIDEO_SETPTS, &vpts32))
        printf("dxr3_decode_video: set video pts failed (%s)\n",
          strerror(errno));
    }
    if (this->stream->xine->verbosity >= XINE_VERBOSITY_DEBUG) {
      if (delay >= 90000)   /* frame more than 1 sec ahead */
	printf("dxr3_decode_video: WARNING: vpts %lld is %.02f seconds ahead of time!\n",
	  vpts, delay/90000.0); 
      if (delay < 0)
	printf("dxr3_decode_video: WARNING: overdue frame.\n");
    }
  }
#if LOG_PTS
  else if (buf->pts) {
    printf("dxr3_decode_video: skip buf->pts = %lld (no vpts)\n", buf->pts);
  }
#endif
  
  /* now write the content to the dxr3 mpeg device and, in a dramatic
   * break with open source tradition, check the return value
   */
  written = write(this->fd_video, buf->content, buf->size);
  if (written < 0) {
    if (errno == EAGAIN) {
      printf("dxr3_decode_video: write to device would block. flushing\n");
      dxr3_flush(this_gen);
    } else {
      printf("dxr3_decode_video: video device write failed (%s)\n",
        strerror(errno));
    }
    return;
  }
  if (written != buf->size)
    printf("dxr3_decode_video: Could only write %d of %d video bytes.\n",
      written, buf->size);
}

static void dxr3_reset(video_decoder_t *this_gen)
{
  dxr3_decoder_t *this = (dxr3_decoder_t *)this_gen;
  
  this->sequence_open = 0;
}

static void dxr3_discontinuity(video_decoder_t *this_gen)
{
}

static void dxr3_flush(video_decoder_t *this_gen) 
{
  dxr3_decoder_t *this = (dxr3_decoder_t *)this_gen;
  
  if (this->sequence_open && ++this->sequence_open > 5 &&
      this->stream->stream_info[XINE_STREAM_INFO_VIDEO_HAS_STILL]) {
    /* The dxr3 needs a sequence end code for still menus to work correctly
     * (the highlights won't move without), but some dvds have stills
     * with no sequence end code. Since it is very likely that flush() is called
     * in still situations, we send one here. */
    static uint8_t end_buffer[4] = { 0x00, 0x00, 0x01, 0xb7 };
    write(this->fd_video, &end_buffer, 4);
    this->sequence_open = 0;
    printf("dxr3_decode_video: WARNING: added missing end sequence\n");
  }
}

static void dxr3_dispose(video_decoder_t *this_gen)
{
  dxr3_decoder_t *this = (dxr3_decoder_t *)this_gen;
  metronom_clock_t *clock = this->class->clock;
  
  if (this->scr)
    clock->unregister_scr(clock, &this->scr->scr_plugin);
  
  dxr3_mvcommand(this->fd_control, MVCOMMAND_FLUSHBUF);
  
  if (this->fd_video >= 0) close(this->fd_video);
  close(this->fd_control);
  
  this->stream->video_out->close(this->stream->video_out, this->stream);
  this->class->instance  = 0;
  
  free(this);
}


static int dxr3_present(xine_stream_t *stream)
{
  plugin_node_t *node;
  video_driver_class_t *vo_class;
  int present = 0;
  
  if (stream->video_driver && stream->video_driver->node) {
    node = (plugin_node_t *)stream->video_driver->node;
    if (node->plugin_class) {
      vo_class = (video_driver_class_t *)node->plugin_class;
      if (vo_class->get_identifier)
        present = (strcmp(vo_class->get_identifier(vo_class), DXR3_VO_ID) == 0);
    }
  }
#if LOG_VID
  printf("dxr3_decode_video: dxr3 %s\n", present ? "present" : "not present");
#endif
  return present;
}

static int dxr3_mvcommand(int fd_control, int command)
{
  em8300_register_t regs;
  
  regs.microcode_register = 1;
  regs.reg = 0;
  regs.val = command;
  
  return ioctl(fd_control, EM8300_IOCTL_WRITEREG, &regs);
}

static void parse_mpeg_header(dxr3_decoder_t *this, uint8_t * buffer)
{
  this->frame_rate_code = buffer[3] & 15;
  this->height          = (buffer[0] << 16) |
                          (buffer[1] <<  8) |
                          (buffer[2] <<  0);
  this->width           = ((this->height >> 12) + 15) & ~15;
  this->height          = ((this->height & 0xfff) + 15) & ~15;
  this->aspect_code     = buffer[3] >> 4;
  
  this->have_header_info = 1;
  
  if (this->force_aspect) this->aspect_code = this->force_aspect;
  
  /* when width, height or aspect changes,
   * we have to send an event for dxr3 spu decoder */
  if (!this->last_width || !this->last_height || !this->last_aspect_code ||
      (this->last_width != this->width) ||
      (this->last_height != this->height) ||
      (this->last_aspect_code != this->aspect_code)) {
    xine_event_t event;
    xine_format_change_data_t data;
    event.type        = XINE_EVENT_FRAME_FORMAT_CHANGE;
    event.stream      = this->stream;
    event.data        = &data;
    event.data_length = sizeof(data);
    data.width        = this->width;
    data.height       = this->height;
    data.aspect       = this->aspect_code;
    data.pan_scan     = this->force_pan_scan;
    xine_event_send(this->stream, &event);
    
    /* update ratio */
    switch (this->aspect_code) {
    case 2:
      this->ratio = 4.0 / 3.0;
      break;
    case 3:
      this->ratio = 16.0 / 9.0;
      break;
    case 4:
      this->ratio = 2.11;
      break;
    default:
      this->ratio = (double)this->width / (double)this->height;
    }
    
    /* update stream metadata */
    this->stream->stream_info[XINE_STREAM_INFO_VIDEO_WIDTH]  = this->width;
    this->stream->stream_info[XINE_STREAM_INFO_VIDEO_HEIGHT] = this->height;
    this->stream->stream_info[XINE_STREAM_INFO_VIDEO_RATIO]  = 10000 * this->ratio;
    
    this->last_width = this->width;
    this->last_height = this->height;
    this->last_aspect_code = this->aspect_code;
  }
}

static int get_duration(dxr3_decoder_t *this)
{
  int duration;
  
  switch (this->frame_rate_code) {
  case 1: /* 23.976 */
    duration = 3754;  /* actually it's 3753.75 */
    break;
  case 2: /* 24.000 */
    duration = 3750;
    break;
  case 3: /* 25.000 */
    duration = this->repeat_first_field ? 5400 : 3600;
    break;
  case 4: /* 29.970 */
    duration = this->repeat_first_field ? 4505 : 3003;
    break;
  case 5: /* 30.000 */
    duration = 3000;
    break;
  case 6: /* 50.000 */
    duration = 1800;
    break;
  case 7: /* 59.940 */
    duration = 1502;  /* actually it's 1501.5 */
    break;
  case 8: /* 60.000 */
    duration = 1500;
    break;
  default:
    printf("dxr3_decode_video: WARNING: unknown frame rate code %d\n",
      this->frame_rate_code);
    duration = 0;
    break;
  }
  
  /* update stream metadata */
  this->stream->stream_info[XINE_STREAM_INFO_FRAME_DURATION] = duration;
  
  if (this->correct_durations && duration) {
    /* we set an initial average frame duration here */
    if (!this->avg_duration) this->avg_duration = duration;
  
    /* Apply a correction to the framerate-code if metronom
     * insists on a different frame duration.
     * The code below is for NTCS streams labeled as PAL streams.
     * (I have seen such things even on dvds!)
     */
    if (this->avg_duration && this->avg_duration < 3300 && duration == 3600) {
      if (this->force_duration_window > 0) {
        /* we are already in a force_duration window, so we force duration */
        this->force_duration_window = FORCE_DURATION_WINDOW_SIZE;
        return 3000;
      }
      if (this->force_duration_window <= 0 && (this->force_duration_window += 10) > 0) {
        /* we just entered a force_duration window, so we start the correction */
        metronom_t *metronom = this->stream->metronom;
        int64_t cur_offset;
        printf("dxr3_decode_video: WARNING: correcting frame rate code from PAL to NTSC\n");
        /* those weird streams need an offset, too */
        cur_offset = metronom->get_option(metronom, METRONOM_AV_OFFSET);
        metronom->set_option(metronom, METRONOM_AV_OFFSET, cur_offset - 28800);
        this->force_duration_window = FORCE_DURATION_WINDOW_SIZE;
        return 3000;
      }
    }
    
    if (this->force_duration_window == -FORCE_DURATION_WINDOW_SIZE)
      /* we are far from a force_duration window */
      return duration;
    if (--this->force_duration_window == 0) {
      /* we have just left a force_duration window */
      metronom_t *metronom = this->stream->metronom;
      int64_t cur_offset;
      cur_offset = metronom->get_option(metronom, METRONOM_AV_OFFSET);
      metronom->set_option(metronom, METRONOM_AV_OFFSET, cur_offset + 28800);
      this->force_duration_window = -FORCE_DURATION_WINDOW_SIZE;
    }
  }
  
  return duration;
}

static void dxr3_update_sync_mode(void *this_gen, xine_cfg_entry_t *entry)
{
  ((dxr3_decoder_t *)this_gen)->sync_every_frame = entry->num_value;
  printf("dxr3_decode_video: setting sync_every_frame to %s\n", 
    (entry->num_value ? "on" : "off"));
}

static void dxr3_update_enhanced_mode(void *this_gen, xine_cfg_entry_t *entry)
{
  ((dxr3_decoder_t *)this_gen)->enhanced_mode = entry->num_value;
  printf("dxr3_decode_video: setting enhanced mode to %s\n", 
    (entry->num_value ? "on" : "off"));
}

static void dxr3_update_correct_durations(void *this_gen, xine_cfg_entry_t *entry)
{
  ((dxr3_decoder_t *)this_gen)->correct_durations = entry->num_value;
  printf("dxr3_decode_video: setting correct_durations mode to %s\n", 
    (entry->num_value ? "on" : "off"));
}
