/*
 * Copyright (C) 2000-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 */

/*
 * Real Media File Demuxer by Mike Melanson (melanson@pcisys.net)
 *   improved by James Stembridge (jstembridge@users.sourceforge.net)
 * For more information regarding the Real file format, visit:
 *   http://www.pcisys.net/~melanson/codecs/
 *
 * video packet sub-demuxer ported from mplayer code (www.mplayerhq.hu):
 *   Real parser & demuxer
 *   
 *   (C) Alex Beregszaszi <alex@naxine.org>
 *   
 *   Based on FFmpeg's libav/rm.c.
 */

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

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define LOG_MODULE "demux_real"
#define LOG_VERBOSE
/*
#define LOG
*/

#include "xine_internal.h"
#include "xineutils.h"
#include "compat.h"
#include "demux.h"
#include "bswap.h"

#define FOURCC_TAG BE_FOURCC
#define RMF_TAG   FOURCC_TAG('.', 'R', 'M', 'F')
#define PROP_TAG  FOURCC_TAG('P', 'R', 'O', 'P')
#define MDPR_TAG  FOURCC_TAG('M', 'D', 'P', 'R')
#define CONT_TAG  FOURCC_TAG('C', 'O', 'N', 'T')
#define DATA_TAG  FOURCC_TAG('D', 'A', 'T', 'A')
#define INDX_TAG  FOURCC_TAG('I', 'N', 'D', 'X')
#define RA_TAG    FOURCC_TAG('.', 'r', 'a', 0xfd)
#define VIDO_TAG  FOURCC_TAG('V', 'I', 'D', 'O')

#define PREAMBLE_SIZE 8
#define REAL_SIGNATURE_SIZE 8
#define DATA_CHUNK_HEADER_SIZE 10
#define DATA_PACKET_HEADER_SIZE 12
#define INDEX_CHUNK_HEADER_SIZE 20
#define INDEX_RECORD_SIZE 14

#define PN_KEYFRAME_FLAG 0x0002

#define MAX_VIDEO_STREAMS 10
#define MAX_AUDIO_STREAMS 8

#define FRAGMENT_TAB_SIZE 256

typedef struct {
  uint16_t   object_version;

  uint16_t   stream_number;
  uint32_t   max_bit_rate;
  uint32_t   avg_bit_rate;
  uint32_t   max_packet_size;
  uint32_t   avg_packet_size;
  uint32_t   start_time;
  uint32_t   preroll;
  uint32_t   duration;
  char       stream_name_size;
  char      *stream_name;
  char       mime_type_size;
  char      *mime_type;
  uint32_t   type_specific_len;
  char      *type_specific_data;
} mdpr_t;

typedef struct {
  unsigned int   timestamp;
  unsigned int   offset;
  unsigned int   packetno;
} real_index_entry_t;

typedef struct {
  uint32_t             fourcc;
  uint32_t             buf_type;
  uint32_t             format;
  
  real_index_entry_t  *index;
  int                  index_entries;
  
  mdpr_t              *mdpr;
} real_stream_t;

typedef struct {
  demux_plugin_t       demux_plugin;

  xine_stream_t       *stream;
  fifo_buffer_t       *video_fifo;
  fifo_buffer_t       *audio_fifo;
  input_plugin_t      *input;
  int                  status;

  off_t                index_start;
  off_t                data_start;
  off_t                data_size;
  unsigned int         duration;

  real_stream_t        video_streams[MAX_VIDEO_STREAMS];
  int                  num_video_streams;
  real_stream_t       *video_stream;

  real_stream_t        audio_streams[MAX_AUDIO_STREAMS];
  int                  num_audio_streams;
  real_stream_t       *audio_stream;
  int                  audio_need_keyframe;

  unsigned int         current_data_chunk_packet_count;
  unsigned int         next_data_chunk_offset;
  unsigned int         data_chunk_size;

  int                  old_seqnum;
  int                  packet_size_cur;

  off_t                avg_bitrate;

  int64_t              last_pts[2];
  int                  send_newpts;
  int                  buf_flag_seek;
  
  uint32_t             last_ts;
  uint32_t             next_ts;
  int                  last_seq;
  int                  next_seq;

  int                  fragment_size; /* video sub-demux */
  int                  fragment_count;
  uint32_t            *fragment_tab;
  int                  fragment_tab_max;

  int                  reference_mode;
} demux_real_t ;

typedef struct {
  demux_class_t     demux_class;
} demux_real_class_t;


static void real_parse_index(demux_real_t *this) {

  off_t                next_index_chunk = this->index_start;
  off_t                original_pos     = this->input->get_current_pos(this->input);
  unsigned char        index_chunk_header[INDEX_CHUNK_HEADER_SIZE];
  unsigned char        index_record[INDEX_RECORD_SIZE];
  int                  i, entries, stream_num;
  real_index_entry_t **index;
  
  while(next_index_chunk) {
    lprintf("reading index chunk at %"PRIX64"\n", next_index_chunk);

    /* Seek to index chunk */
    this->input->seek(this->input, next_index_chunk, SEEK_SET);

    /* Read index chunk header */
    if(this->input->read(this->input, index_chunk_header, INDEX_CHUNK_HEADER_SIZE)
       != INDEX_CHUNK_HEADER_SIZE) {
      lprintf("index chunk header not read\n");
      break;
    }

    /* Check chunk is actually an index chunk */
    if(_X_BE_32(&index_chunk_header[0]) == INDX_TAG) {
      unsigned short version;

      /* Check version */
      version = _X_BE_16(&index_chunk_header[8]);
      if(version != 0) {
        xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                "demux_real: unknown object version in INDX: 0x%04x\n", version);
        break;
      }

      /* Read data from header */
      entries          = _X_BE_32(&index_chunk_header[10]);
      stream_num       = _X_BE_16(&index_chunk_header[14]);
      next_index_chunk = _X_BE_32(&index_chunk_header[16]);

      /* Find which stream this index is for */
      index = NULL;
      for(i = 0; i < this->num_video_streams; i++) {
        if(stream_num == this->video_streams[i].mdpr->stream_number) {
          index = &this->video_streams[i].index;
          this->video_streams[i].index_entries = entries;
          lprintf("found index chunk for video stream with num %d\n", stream_num);
          break;
        }
      }

      if(!index) {
        for(i = 0; i < this->num_audio_streams; i++) {
          if(stream_num == this->audio_streams[i].mdpr->stream_number) {
            index = &this->audio_streams[i].index;
            this->audio_streams[i].index_entries = entries;
            lprintf("found index chunk for audio stream with num %d\n", stream_num);
            break;
          }
        }
      }

      if(index && entries) {
        /* Allocate memory for index */
        *index = xine_xmalloc(entries * sizeof(real_index_entry_t));
        
        /* Read index */
        for(i = 0; i < entries; i++) {
          if(this->input->read(this->input, index_record, INDEX_RECORD_SIZE)
             != INDEX_RECORD_SIZE) {
            lprintf("index record not read\n");
            free(*index);
            *index = NULL;
            break;
          }

          (*index)[i].timestamp = _X_BE_32(&index_record[2]);
          (*index)[i].offset    = _X_BE_32(&index_record[6]);
          (*index)[i].packetno  = _X_BE_32(&index_record[10]);
        }
      } else {
        lprintf("unused index chunk with %d entries for stream num %d\n",
                entries, stream_num);
      }
    } else {
      lprintf("expected index chunk found chunk type: %.4s\n", &index_chunk_header[0]);
      break;
    }
  }

  /* Seek back to position before index reading */
  this->input->seek(this->input, original_pos, SEEK_SET);
}

static mdpr_t *real_parse_mdpr(const char *data) {
  mdpr_t *mdpr=malloc(sizeof(mdpr_t));

  mdpr->stream_number=_X_BE_16(&data[2]);
  mdpr->max_bit_rate=_X_BE_32(&data[4]);
  mdpr->avg_bit_rate=_X_BE_32(&data[8]);
  mdpr->max_packet_size=_X_BE_32(&data[12]);
  mdpr->avg_packet_size=_X_BE_32(&data[16]);
  mdpr->start_time=_X_BE_32(&data[20]);
  mdpr->preroll=_X_BE_32(&data[24]);
  mdpr->duration=_X_BE_32(&data[28]);

  mdpr->stream_name_size=data[32];
  mdpr->stream_name=malloc(sizeof(char)*(mdpr->stream_name_size+1));
  memcpy(mdpr->stream_name, &data[33], mdpr->stream_name_size);
  mdpr->stream_name[(int)mdpr->stream_name_size]=0;

  mdpr->mime_type_size=data[33+mdpr->stream_name_size];
  mdpr->mime_type=malloc(sizeof(char)*(mdpr->mime_type_size+1));
  memcpy(mdpr->mime_type, &data[34+mdpr->stream_name_size], mdpr->mime_type_size);
  mdpr->mime_type[(int)mdpr->mime_type_size]=0;

  mdpr->type_specific_len=_X_BE_32(&data[34+mdpr->stream_name_size+mdpr->mime_type_size]);
  mdpr->type_specific_data=malloc(sizeof(char)*(mdpr->type_specific_len));
  memcpy(mdpr->type_specific_data,
      &data[38+mdpr->stream_name_size+mdpr->mime_type_size], mdpr->type_specific_len);

  lprintf("MDPR: stream number: %i\n", mdpr->stream_number);
  lprintf("MDPR: maximal bit rate: %i\n", mdpr->max_bit_rate);
  lprintf("MDPR: average bit rate: %i\n", mdpr->avg_bit_rate);
  lprintf("MDPR: largest packet size: %i bytes\n", mdpr->max_packet_size);
  lprintf("MDPR: average packet size: %i bytes\n", mdpr->avg_packet_size);
  lprintf("MDPR: start time: %i\n", mdpr->start_time);
  lprintf("MDPR: pre-buffer: %i ms\n", mdpr->preroll);
  lprintf("MDPR: duration of stream: %i ms\n", mdpr->duration);
  lprintf("MDPR: stream name: %s\n", mdpr->stream_name);
  lprintf("MDPR: mime type: %s\n", mdpr->mime_type);
  lprintf("MDPR: type specific data:\n");
#ifdef LOG
  xine_hexdump(mdpr->type_specific_data, mdpr->type_specific_len);
#endif

  return mdpr;
}

static void real_free_mdpr (mdpr_t *mdpr) {
  free (mdpr->stream_name);
  free (mdpr->mime_type);
  free (mdpr->type_specific_data);
  free (mdpr);
}


static void real_parse_headers (demux_real_t *this) {

  char           preamble[PREAMBLE_SIZE];
  unsigned int   chunk_type = 0;
  unsigned int   chunk_size;
  unsigned short version;
  unsigned char *chunk_buffer;
  int            field_size;
  int            stream_ptr;
  unsigned char  data_chunk_header[DATA_CHUNK_HEADER_SIZE];
  unsigned char  signature[REAL_SIGNATURE_SIZE];

  this->data_start = 0;
  this->data_size = 0;
  this->num_video_streams = 0;
  this->num_audio_streams = 0;

  if (INPUT_IS_SEEKABLE(this->input))
    this->input->seek (this->input, 0, SEEK_SET);

  if (this->input->read(this->input, signature, REAL_SIGNATURE_SIZE) !=
      REAL_SIGNATURE_SIZE) {

    lprintf ("signature not read\n");
    this->status = DEMUX_FINISHED;
    return;
  }

  if (_X_BE_32(signature) != RMF_TAG) {
    this->status = DEMUX_FINISHED;
    lprintf ("signature not found '%.4s'\n", signature);
    return;
  }

  /* skip to the start of the first chunk and start traversing */
  chunk_size = _X_BE_32(&signature[4]);
  this->input->seek(this->input, chunk_size-8, SEEK_CUR);

  /* iterate through chunks and gather information until the first DATA
   * chunk is located */
  while (chunk_type != DATA_TAG) {

    if (this->input->read(this->input, preamble, PREAMBLE_SIZE) !=
	PREAMBLE_SIZE) {
      this->status = DEMUX_FINISHED;
      return;
    }
    chunk_type = _X_BE_32(&preamble[0]);
    chunk_size = _X_BE_32(&preamble[4]);

    lprintf ("chunktype %.4s len %d\n", (char *) &chunk_type, chunk_size);
    switch (chunk_type) {

    case PROP_TAG:
    case MDPR_TAG:
    case CONT_TAG:

      chunk_size -= PREAMBLE_SIZE;
      chunk_buffer = xine_xmalloc(chunk_size);
      if (this->input->read(this->input, chunk_buffer, chunk_size) !=
	  chunk_size) {
	free (chunk_buffer);
	this->status = DEMUX_FINISHED;
	return;
      }
      
      version = _X_BE_16(&chunk_buffer[0]);

      if (chunk_type == PROP_TAG) {
      
        if(version != 0) {
          xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                  "demuxe_real: unknown object version in PROP: 0x%04x\n", version);
          free(chunk_buffer);
          this->status = DEMUX_FINISHED;
          return;
        }

        this->duration      = _X_BE_32(&chunk_buffer[22]);
        this->index_start   = _X_BE_32(&chunk_buffer[30]);
        this->data_start    = _X_BE_32(&chunk_buffer[34]);
	this->avg_bitrate   = _X_BE_32(&chunk_buffer[6]);

        lprintf("PROP: duration: %d ms\n", this->duration);
        lprintf("PROP: index start: %"PRIX64"\n", this->index_start);
        lprintf("PROP: data start: %"PRIX64"\n", this->data_start);
        lprintf("PROP: average bit rate: %"PRId64"\n", this->avg_bitrate);

	if (this->avg_bitrate<1)
	  this->avg_bitrate = 1;

	_x_stream_info_set(this->stream, XINE_STREAM_INFO_BITRATE,
                             this->avg_bitrate);

      } else if (chunk_type == MDPR_TAG) {

        mdpr_t   *mdpr;
        uint32_t  fourcc;

        if (version != 0) {
          xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                  "demux_real: unknown object version in MDPR: 0x%04x\n", version);
          free(chunk_buffer);
          continue;
        }
                
        mdpr = real_parse_mdpr (chunk_buffer);

        lprintf ("parsing type specific data...\n");

        if(_X_BE_32(mdpr->type_specific_data) == RA_TAG) {
          int version, len;

          if(this->num_audio_streams == MAX_AUDIO_STREAMS) {
            xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                    "demux_real: maximum number of audio stream exceeded\n");
            goto unknown;
          }
          
          version = _X_BE_16(mdpr->type_specific_data + 4);

          lprintf("audio version %d detected\n", version);

          switch(version) {
            case 3:
              /* Version 3 header stores fourcc after meta info - cheat by reading backwards from the 
               * end of the header instead of having to parse it all */
              fourcc = _X_ME_32(mdpr->type_specific_data + mdpr->type_specific_len - 5);
              break;
            case 4:
              len = *(mdpr->type_specific_data + 56);
              fourcc = _X_ME_32(mdpr->type_specific_data + 58 + len);
              break;
            case 5:
              fourcc = _X_ME_32(mdpr->type_specific_data + 66);
              break;
            default:
              lprintf("unsupported audio header version %d\n", version);
              goto unknown;
          }

          lprintf("fourcc = %.4s\n", (char *) &fourcc);

          this->audio_streams[this->num_audio_streams].fourcc = fourcc;
          this->audio_streams[this->num_audio_streams].buf_type = _x_formattag_to_buf_audio(fourcc);
          this->audio_streams[this->num_audio_streams].index = NULL;
          this->audio_streams[this->num_audio_streams].mdpr = mdpr;

          this->num_audio_streams++;

        } else if(_X_BE_32(mdpr->type_specific_data + 4) == VIDO_TAG) {

          if(this->num_video_streams == MAX_VIDEO_STREAMS) {
            xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                    "demux_real: maximum number of video stream exceeded\n");
            goto unknown;
          }
          
          lprintf ("video detected\n");
          fourcc = _X_ME_32(mdpr->type_specific_data + 8);
          lprintf("fourcc = %.4s\n", (char *) &fourcc);

          this->video_streams[this->num_video_streams].fourcc = fourcc;
          this->video_streams[this->num_video_streams].buf_type = _x_fourcc_to_buf_video(fourcc);
          this->video_streams[this->num_video_streams].format = _X_BE_32(mdpr->type_specific_data + 30);
          this->video_streams[this->num_video_streams].index = NULL;
          this->video_streams[this->num_video_streams].mdpr = mdpr;

          this->num_video_streams++;

        } else {
          lprintf("unrecognised type specific data\n");

unknown:
          real_free_mdpr(mdpr);
        }

      } else if (chunk_type == CONT_TAG) {

        if(version != 0) {
          xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                  "demux_real: unknown object version in CONT: 0x%04x\n", version);
          free(chunk_buffer);
          continue;
        }

        stream_ptr = 2;

        /* load the title string */
        field_size = _X_BE_16(&chunk_buffer[stream_ptr]);
        stream_ptr += 2;
        _x_meta_info_n_set(this->stream, XINE_META_INFO_TITLE,
                            &chunk_buffer[stream_ptr], field_size);
        stream_ptr += field_size;

        /* load the author string */
        field_size = _X_BE_16(&chunk_buffer[stream_ptr]);
        stream_ptr += 2;
        _x_meta_info_n_set(this->stream, XINE_META_INFO_ARTIST,
                            &chunk_buffer[stream_ptr], field_size);
        stream_ptr += field_size;

        /* load the copyright string as the year */
        field_size = _X_BE_16(&chunk_buffer[stream_ptr]);
        stream_ptr += 2;
        _x_meta_info_n_set(this->stream, XINE_META_INFO_YEAR,
                            &chunk_buffer[stream_ptr], field_size);
        stream_ptr += field_size;

        /* load the comment string */
        field_size = _X_BE_16(&chunk_buffer[stream_ptr]);
        stream_ptr += 2;
        _x_meta_info_n_set(this->stream, XINE_META_INFO_COMMENT,
                            &chunk_buffer[stream_ptr], field_size);
        stream_ptr += field_size;
      }

      free(chunk_buffer);
      break;

    case DATA_TAG:
      if (this->input->read(this->input, data_chunk_header, 
                            DATA_CHUNK_HEADER_SIZE) != DATA_CHUNK_HEADER_SIZE) {
        this->status = DEMUX_FINISHED;
        return ;
      }
      
      /* check version */
      version = _X_BE_16(&data_chunk_header[0]);
      if(version != 0) {
          xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                  "demux_real: unknown object version in DATA: 0x%04x\n", version);
          this->status = DEMUX_FINISHED;
          return;
      }

      this->current_data_chunk_packet_count = _X_BE_32(&data_chunk_header[2]);
      this->next_data_chunk_offset = _X_BE_32(&data_chunk_header[6]);
      this->data_chunk_size = chunk_size;
      break;

    default:
      /* this should not occur, but in case it does, skip the chunk */
      lprintf("skipping a chunk!\n");
      this->input->seek(this->input, chunk_size - PREAMBLE_SIZE, SEEK_CUR);
      break;

    }
  }

  /* Read index tables */
  if(INPUT_IS_SEEKABLE(this->input))
    real_parse_index(this);
  
  /* Simple stream selection case - 0/1 audio/video streams */
  if(this->num_video_streams == 1)
    this->video_stream = &this->video_streams[0];
  else
    this->video_stream = NULL;
    
  if(this->num_audio_streams == 1)
    this->audio_stream = &this->audio_streams[0];
  else
    this->audio_stream = NULL;

  /* In the case of multiple audio/video streams select the first
     streams found in the file */
  if((this->num_video_streams > 1) || (this->num_audio_streams > 1)) {
    int   len, offset;
    off_t original_pos = 0;
    char  search_buffer[MAX_PREVIEW_SIZE];

    /* Get data to search through for stream chunks */
    if(INPUT_IS_SEEKABLE(this->input)) {
      original_pos = this->input->get_current_pos(this->input);
    
      if((len = this->input->read(this->input, search_buffer, MAX_PREVIEW_SIZE)) <= 0) {
        xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
                "demux_real: failed to read header\n");
        this->status = DEMUX_FINISHED;
        return;
      }
      
      offset = 0;
    } else if((this->input->get_capabilities(this->input) & INPUT_CAP_PREVIEW) != 0) {
      if((len = this->input->get_optional_data(this->input, search_buffer, INPUT_OPTIONAL_DATA_PREVIEW)) <= 0) {
        xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
                "demux_real: failed to read header\n");
        this->status = DEMUX_FINISHED;
        return;
      }
      
      /* Preview data starts at the beginning of the file */
      offset = this->data_start + 18;
    } else {
      xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
              "demux_real: unable to search for correct stream\n");
      this->status = DEMUX_FINISHED;
      return;
    }
    
    while((offset < len) &&
          ((!this->video_stream && (this->num_video_streams > 0)) ||
           (!this->audio_stream && (this->num_audio_streams > 0)))) {
      uint32_t id;
      int      i, stream;
      
      /* Check for end of the data chunk */
      if(((id = _X_BE_32(&search_buffer[offset])) == DATA_TAG) ||
         (id == INDX_TAG))
        break;
      
      stream = _X_BE_16(&search_buffer[offset + 4]);

      for(i = 0; !this->video_stream && (i < this->num_video_streams); i++) {
        if(stream == this->video_streams[i].mdpr->stream_number) {
          this->video_stream = &this->video_streams[i];
          lprintf("selecting video stream: %d\n", stream);
        }
      }
      
      for(i = 0; !this->audio_stream && (i < this->num_audio_streams); i++) {
        if(stream == this->audio_streams[i].mdpr->stream_number) {
          this->audio_stream = &this->audio_streams[i];
          lprintf("selecting audio stream: %d\n", stream);
        }
      }

      offset += _X_BE_16(&search_buffer[offset + 2]);
    }
    
    if(INPUT_IS_SEEKABLE(this->input))
      this->input->seek(this->input, original_pos, SEEK_SET);
  }
  
  /* Let the user know if we haven't managed to detect what streams to play */
  if((!this->video_stream && this->num_video_streams) ||
     (!this->audio_stream && this->num_audio_streams)) {
    xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
            "demux_real: unable to determine which audio/video streams to play\n");
    this->status = DEMUX_FINISHED;
    return;
  }
    
  /* Send headers and set meta info */
  if(this->video_stream) {
    buf_element_t *buf;
    
    /* Check for recognised codec*/
    if(!this->video_stream->buf_type)
      this->video_stream->buf_type = BUF_VIDEO_UNKNOWN;
      
    /* Send header */
    buf = this->video_fifo->buffer_pool_alloc(this->video_fifo);
    buf->content = buf->mem;
    
    memcpy(buf->content, this->video_stream->mdpr->type_specific_data,
           this->video_stream->mdpr->type_specific_len);

    buf->size                   = this->video_stream->mdpr->type_specific_len;
    buf->decoder_flags          = BUF_FLAG_HEADER|BUF_FLAG_FRAME_END;
    buf->type                   = this->video_stream->buf_type;
    buf->extra_info->input_normpos = 0;
    buf->extra_info->input_time    = 0;

    this->video_fifo->put (this->video_fifo, buf);

    /* Set meta info */
    _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, 1);
    _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_FOURCC,
                         this->video_stream->fourcc);
    _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_BITRATE,
                         this->video_stream->mdpr->avg_bit_rate);
                         
    /* Allocate fragment offset table */
    this->fragment_tab = xine_xmalloc(FRAGMENT_TAB_SIZE*sizeof(uint32_t));
    this->fragment_tab_max = FRAGMENT_TAB_SIZE;
  }

  if(this->audio_stream) {
    /* Check for recognised codec */
    if(!this->audio_stream->buf_type)
      this->audio_stream->buf_type = BUF_AUDIO_UNKNOWN;
  
    /* Send headers */
    if(this->audio_fifo) {
      mdpr_t        *mdpr = this->audio_stream->mdpr;
      buf_element_t *buf;

      buf = this->audio_fifo->buffer_pool_alloc (this->audio_fifo);

      buf->type           = this->audio_stream->buf_type;
      buf->decoder_flags  = BUF_FLAG_HEADER|BUF_FLAG_FRAME_END;
      
      /* For AAC we send two header buffers, the first is a standard audio
       * header giving bits per sample, sample rate and number of channels.
       * The second is the codec initialisation data found at the end of
       * the type specific data for the audio stream */
      if(buf->type == BUF_AUDIO_AAC) {
        int version = _X_BE_16(mdpr->type_specific_data + 4);
        
        if(version != 5) {
          xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
                  "demux_real: unsupported audio header version for AAC: %d\n", version);
          buf->free_buffer(buf);
          goto unsupported;
        }
        
        buf->decoder_info[1] = _X_BE_16(mdpr->type_specific_data + 54);
        buf->decoder_info[2] = _X_BE_16(mdpr->type_specific_data + 58);
        buf->decoder_info[3] = _X_BE_16(mdpr->type_specific_data + 60);

        buf->decoder_flags |= BUF_FLAG_STDHEADER;
        buf->content        = NULL;
        buf->size           = 0;
                
        this->audio_fifo->put (this->audio_fifo, buf);
        
        buf = this->audio_fifo->buffer_pool_alloc (this->audio_fifo);
        
        buf->type                = this->audio_stream->buf_type;
        buf->decoder_flags       = BUF_FLAG_HEADER|BUF_FLAG_FRAME_END|BUF_FLAG_SPECIAL;
        buf->decoder_info[1]     = BUF_SPECIAL_DECODER_CONFIG;
        buf->decoder_info[2]     = _X_BE_32(mdpr->type_specific_data + 74) - 1;
        buf->decoder_info_ptr[2] = buf->content;
        buf->size                = 0;
                        
        memcpy(buf->content, mdpr->type_specific_data + 79,
               buf->decoder_info[2]);

      } else {
        memcpy(buf->content, mdpr->type_specific_data,
               mdpr->type_specific_len);

        buf->size           = mdpr->type_specific_len;
      }

      this->audio_fifo->put (this->audio_fifo, buf);
    }

unsupported:
    /* Set meta info */
    _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, 1);
    _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_FOURCC,
                         this->audio_stream->fourcc);
    _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE,
                         this->audio_stream->mdpr->avg_bit_rate);
  }
}


/* very naive approach for parsing ram files. it will extract known
 * mrls directly so it should work for simple smil files too.
 * no attempt is made to support smil features:
 * http://service.real.com/help/library/guides/production/htmfiles/smil.htm
 */
static int demux_real_parse_references( demux_real_t *this) {

  char           *buf = NULL;
  int             buf_size = 0;
  int             buf_used = 0;
  int             len, i, j;
  int             alternative = 0;
  int             comment = 0;


  lprintf("parsing references\n");
  
  /* read file to memory.
   * warning: dumb code, but hopefuly ok since reference file is small */
  do {
    buf_size += 1024;
    buf = realloc(buf, buf_size+1);

    len = this->input->read(this->input, &buf[buf_used], buf_size-buf_used);

    if( len > 0 )
      buf_used += len;

    /* 50k of reference file? no way. something must be wrong */
    if( buf_used > 50*1024 )
      break;
  } while( len > 0 );

  if(buf_used)
    buf[buf_used] = '\0';
  
  lprintf("received %d bytes [%s]\n", buf_used, buf);

  if (!strncmp(buf,"http://",7))
  {
    for (i = 0; buf[i] && !isspace(buf[i]); ++i)
      /**/;
    buf[i] = 0;
    lprintf("reference [%s] found\n", buf);

    _x_demux_send_mrl_reference (this->stream, 0, buf, NULL, 0, 0);
  }
  else for (i = 0; i < buf_used; ++i)
  {
    /* "--stop--" is used to have pnm alternative for old real clients
     * new real clients will stop processing the file and thus use
     * rtsp protocol.
     */
    if( !strncmp(&buf[i],"--stop--",8) )
      alternative++;

    /* rpm files can contain comments which should be skipped */
    if( !strncmp(&buf[i],"<!--",4) )
      comment = 1;
      
    if( !strncmp(&buf[i],"-->",3) )
      comment = 0;
      
    if( (!strncmp(&buf[i],"pnm://",6) || !strncmp(&buf[i],"rtsp://",7)) &&
        !comment ) {
      for(j=i; buf[j] && buf[j] != '"' && !isspace(buf[j]); j++ )
        ;
      buf[j]='\0';
      lprintf("reference [%s] found\n", &buf[i]);

      _x_demux_send_mrl_reference (this->stream, alternative,
                                   &buf[i], NULL, 0, 0);

      i = j;
    }
  }  
  
  free(buf);
  
  this->status = DEMUX_FINISHED;
  return this->status;
}
        
/* redefine abs as macro to handle 64-bit diffs.
   i guess llabs may not be available everywhere */
#define abs(x) ( ((x)<0) ? -(x) : (x) )

#define WRAP_THRESHOLD           220000
#define PTS_AUDIO                0
#define PTS_VIDEO                1

static void check_newpts (demux_real_t *this, int64_t pts, int video, int preview) {
  int64_t diff;

  diff = pts - this->last_pts[video];
  lprintf ("check_newpts %"PRId64"\n", pts);

  if (!preview && pts &&
      (this->send_newpts || (this->last_pts[video] && abs(diff)>WRAP_THRESHOLD) ) ) {

    lprintf ("diff=%"PRId64"\n", diff);

    if (this->buf_flag_seek) {
      _x_demux_control_newpts(this->stream, pts, BUF_FLAG_SEEK);
      this->buf_flag_seek = 0;
    } else {
      _x_demux_control_newpts(this->stream, pts, 0);
    }
    this->send_newpts = 0;
    this->last_pts[1-video] = 0;
  }

  if (!preview && pts )
    this->last_pts[video] = pts;
}

static uint32_t real_fix_timestamp (demux_real_t *this, uint8_t *hdr, uint32_t ts_in) {
  int      pict_type;
  int      seq;
  uint32_t ts_out;
        
  switch(this->video_stream->buf_type) {
    case BUF_VIDEO_RV20:
      pict_type = (hdr[0] & 0xC0) >> 6;
      seq       = ((hdr[1] & 0x7F) << 6) + ((hdr[2] & 0xFC) >> 2);
      break;
    case BUF_VIDEO_RV30:
      pict_type = (hdr[0] & 0x18) >> 3;
      seq       = ((hdr[1] & 0x0F) << 9) + (hdr[2] << 1) + ((hdr[3] & 0x80) >> 7);
      break;
    case BUF_VIDEO_RV40:
      pict_type = (hdr[0] & 0x60) >> 5;
      seq       = ((hdr[1] & 0x07) << 10) + (hdr[2] << 2) + ((hdr[3] & 0xC0) >> 6);
      break;
    default:
      xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
              "demux_real: can't fix timestamp for buf type 0x%08x\n", 
              this->video_stream->buf_type);
      return ts_in;
      break;
  }
            
  switch (pict_type) {
    case 0:
    case 1: 
      /* I frame */
      ts_out = this->next_ts;
            
      this->last_ts = this->next_ts;
      this->next_ts = ts_in;
            
      this->last_seq = this->next_seq;
      this->next_seq = seq;
      break;
    case 2:
      /* P frame */
      ts_out = this->next_ts;
                
      this->last_ts  = this->next_ts;
      if (seq < this->next_seq)
        this->next_ts += seq + 8192 - this->next_seq;
      else
        this->next_ts += seq - this->next_seq;
                
      this->last_seq = this->next_seq;
      this->next_seq = seq;
      break;
    case 3:
      /* B frame */
      if (seq < this->last_seq)
        ts_out = ((seq + 8192 - this->last_seq) + this->last_ts);
      else
        ts_out = ((seq - this->last_seq) + this->last_ts);
      break;
    default:
      xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
              "demux_real: unknown pict_type: %d\n", pict_type);
      ts_out = 0;
      break;
  }
  
  return ts_out;            
}

static int stream_read_char (demux_real_t *this) {
  uint8_t ret;
  this->input->read (this->input, &ret, 1);
  return ret;
}

static int stream_read_word (demux_real_t *this) {
  uint16_t ret;
  this->input->read (this->input, (char *) &ret, 2);
  return _X_BE_16(&ret);
}

static int demux_real_send_chunk(demux_plugin_t *this_gen) {

  demux_real_t   *this = (demux_real_t *) this_gen;
  char            header[DATA_PACKET_HEADER_SIZE];
  int             stream, size, keyframe, input_time = 0;
  unsigned short  version;
  uint32_t        id, timestamp;
  int64_t         pts;
  off_t           offset, input_length = 0;
  int             normpos = 0;

  if(this->reference_mode)
    return demux_real_parse_references(this);
    
  /* load a header from wherever the stream happens to be pointing */
  if ( (size=this->input->read(this->input, header, DATA_PACKET_HEADER_SIZE)) !=
      DATA_PACKET_HEADER_SIZE) {

    xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG,
             "demux_real: failed to read data packet header\n");

    this->status = DEMUX_FINISHED;
    return this->status;
  }

  /* Check to see if we've gone past the end of the data chunk */
  if(((id = _X_BE_32(&header[0])) == DATA_TAG) ||
     (id == INDX_TAG)) {
    lprintf("finished reading data chunk\n");
    this->status = DEMUX_FINISHED;
    return this->status;
  }

  /* check version */
  version = _X_BE_16(&header[0]);
  if(version > 1) {
    xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
            "demux_real: unknown object version in data packet: 0x%04x\n", version);
    this->status = DEMUX_FINISHED;
    return this->status;
  }
  
  /* read the packet information */
  stream   = _X_BE_16(&header[4]);
  offset   = this->input->get_current_pos(this->input);
  size     = _X_BE_16(&header[2]) - DATA_PACKET_HEADER_SIZE;
  timestamp= _X_BE_32(&header[6]);
  pts      = (int64_t) timestamp * 90;
  
  /* Data packet header with version 1 contains 1 extra byte */
  if(version == 0)
    keyframe = header[11] & PN_KEYFRAME_FLAG;
  else {
    keyframe = stream_read_char(this) & PN_KEYFRAME_FLAG;
    size--;
  }

  lprintf ("packet of stream %d, 0x%X bytes @ %"PRIX64", pts = %"PRId64"%s\n",
           stream, size, offset, pts, keyframe ? ", keyframe" : "");

  if (this->video_stream && (stream == this->video_stream->mdpr->stream_number)) {

    int            vpkg_header, vpkg_length, vpkg_offset;
    int            vpkg_seqnum = -1;
    int            vpkg_subseq = 0;
    buf_element_t *buf;
    int            n, fragment_size;
    uint32_t       decoder_flags;

    lprintf ("video chunk detected.\n");

    /* sub-demuxer */

    while (size > 2) {

      /*
       * read packet header
       * bit 7: 1=last block in block chain
       * bit 6: 1=short header (only one block?)
       */

      vpkg_header = stream_read_char (this); size--;
      lprintf ("vpkg_hdr: %02x (size=%d)\n", vpkg_header, size);

      if (0x40==(vpkg_header&0xc0)) {
	/*
	 * seems to be a very short header
	 * 2 bytes, purpose of the second byte yet unknown
	 */
	 int bummer;

	 bummer = stream_read_char (this); size--;
	 lprintf ("bummer == %02X\n",bummer);

	 vpkg_offset = 0;
	 vpkg_length = size;

      } else {

	if (0==(vpkg_header & 0x40)) {
	  /* sub-seqnum (bits 0-6: number of fragment. bit 7: ???) */
	  vpkg_subseq = stream_read_char (this); size--;

	  vpkg_subseq &= 0x7f;
	}

	/*
	 * size of the complete packet
	 * bit 14 is always one (same applies to the offset)
	 */
	vpkg_length = stream_read_word (this); size -= 2;
	if (!(vpkg_length&0xC000)) {
	  vpkg_length <<= 16;
	  vpkg_length |=  stream_read_word (this);
	  size-=2;
	} else
	  vpkg_length &= 0x3fff;

	/*
	 * offset of the following data inside the complete packet
	 * Note: if (hdr&0xC0)==0x80 then offset is relative to the
	 * _end_ of the packet, so it's equal to fragment size!!!
	 */

	vpkg_offset = stream_read_word (this); size -= 2;

	if (!(vpkg_offset&0xC000)) {
	  vpkg_offset <<= 16;
	  vpkg_offset |=  stream_read_word (this);
	  size -= 2;
	} else
	  vpkg_offset &= 0x3fff;

	vpkg_seqnum = stream_read_char (this); size--;
      }

      lprintf ("seq=%d, offset=%d, length=%d, size=%d, frag size=%d, flags=%02x\n",
               vpkg_seqnum, vpkg_offset, vpkg_length, size, this->fragment_size,
               vpkg_header);

      if (vpkg_seqnum != this->old_seqnum) {
        lprintf ("new seqnum\n");

	this->fragment_size = 0;
	this->old_seqnum = vpkg_seqnum;
      }

      /* if we have a seekable stream then use the timestamp for the data
       * packet for more accurate seeking - if not then estimate time using
       * average bitrate */
      if(this->video_stream->index)
        input_time = timestamp;
      else
        input_time = (int)((int64_t) this->input->get_current_pos(this->input) 
                           * 8 * 1000 / this->avg_bitrate);

      if(this->data_start && this->data_chunk_size)
        input_length = this->data_start + 18 + this->data_chunk_size;
      if( input_length )
        normpos = (int)((double) this->input->get_current_pos(this->input) * 65535 / input_length);
        
      check_newpts (this, pts, PTS_VIDEO, 0);

      if (this->fragment_size == 0) {
	lprintf ("new packet starting\n");
        
        /* send fragment offset table */
        if(this->fragment_count) {
          lprintf("sending fragment offset table\n");
        
          buf = this->video_fifo->buffer_pool_alloc(this->video_fifo);

          buf->decoder_flags = BUF_FLAG_SPECIAL | BUF_FLAG_FRAME_END;
          buf->decoder_info[1] = BUF_SPECIAL_RV_CHUNK_TABLE;
          buf->decoder_info[2] = this->fragment_count - 1;
          buf->decoder_info_ptr[2] = buf->content;
          buf->decoder_info[3] = 0;
          buf->size = 0;
          buf->type = this->video_stream->buf_type;

          xine_fast_memcpy(buf->decoder_info_ptr[2], this->fragment_tab,
                           this->fragment_count*8);
          
          this->video_fifo->put(this->video_fifo, buf);
        
          this->fragment_count = 0;
        }
        
	decoder_flags = BUF_FLAG_FRAME_START;
      } else {
	lprintf ("continuing packet \n");
	decoder_flags = 0;
      }
      
      /* add entry to fragment offset table */
      this->fragment_tab[2*this->fragment_count]   = 1;
      this->fragment_tab[2*this->fragment_count+1] = this->fragment_size;
      this->fragment_count++;

      /*
       * calc size of fragment
       */

      if ((vpkg_header & 0xc0) == 0x080)
	fragment_size = vpkg_offset;
      else {
	if (0x00 == (vpkg_header&0xc0))
	  fragment_size = size;
	else
	  fragment_size = vpkg_length;
      }
      lprintf ("fragment size is %d\n", fragment_size);

      /*
       * read fragment_size bytes of data
       */
       
      n = fragment_size;
      while(n) {
        buf = this->video_fifo->buffer_pool_alloc(this->video_fifo);
        
        if(n>buf->max_size)
          buf->size          = buf->max_size;
        else
          buf->size          = n;

        buf->decoder_flags = decoder_flags;
        decoder_flags &= ~BUF_FLAG_FRAME_START;
        
        buf->type = this->video_stream->buf_type;
      
        if(this->input->read(this->input, buf->content, buf->size) < buf->size) {
          xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
                  "demux_real: failed to read video fragment");
          buf->free_buffer(buf);
          this->status = DEMUX_FINISHED;
          return this->status;
        }
        
        /* RV30 and RV40 streams contain some fragments that shouldn't be passed 
         * to the decoder. The purpose of these fragments is unknown, but 
         * realplayer doesn't appear to pass them to the decoder either */        
        if((n == fragment_size) && 
           (((buf->type == BUF_VIDEO_RV30) && (buf->content[0] & 0x20)) ||
            ((buf->type == BUF_VIDEO_RV40) && (buf->content[0] & 0x80)))) {
          lprintf("ignoring fragment\n");

          /* Discard buffer and skip over rest of fragment */
          buf->free_buffer(buf);
          this->input->seek(this->input, n - buf->size, SEEK_CUR);
          this->fragment_count--;

          break;
        }
          
        /* if the video stream has b-frames fix the timestamps */
        if((this->video_stream->format >= 0x20200002) &&
           (buf->decoder_flags & BUF_FLAG_FRAME_START))
          pts = (int64_t) real_fix_timestamp(this, buf->content, timestamp) * 90;
        
        /* this test was moved from ffmpeg video decoder.
         * fixme: is pts only valid on frame start? */
        if( buf->decoder_flags & BUF_FLAG_FRAME_START )
          buf->pts = pts;
        else
          buf->pts = 0;
        pts = 0;
        
        buf->extra_info->input_normpos = normpos;
        buf->extra_info->input_time    = input_time;
        buf->extra_info->total_time    = this->duration;
        
        this->video_fifo->put(this->video_fifo, buf);
        
        n -= buf->size;
      }

      size -= fragment_size;
      lprintf ("size left %d\n", size);

      this->fragment_size += fragment_size;

      if (this->fragment_size >= vpkg_length) {
        lprintf ("fragment finished (%d/%d)\n", this->fragment_size, vpkg_length);
        this->fragment_size = 0;
      }

    } /* while(size>2) */

  } else if (this->audio_fifo && this->audio_stream &&
             (stream == this->audio_stream->mdpr->stream_number)) {

    lprintf ("audio chunk detected.\n");

    if(this->audio_need_keyframe && !keyframe)
      goto discard;
    else
      this->audio_need_keyframe = 0;
    
    /* if we have a seekable stream then use the timestamp for the data
     * packet for more accurate seeking - if not then estimate time using
     * average bitrate */
    if(this->audio_stream->index)
      input_time = timestamp;
    else
      input_time = (int)((int64_t) this->input->get_current_pos(this->input) 
                         * 8 * 1000 / this->avg_bitrate);     
    
    if(this->data_start && this->data_chunk_size)
      input_length = this->data_start + 18 + this->data_chunk_size;
    else
      input_length = 0;
    
    if( input_length )
      normpos = (int)((double) this->input->get_current_pos(this->input) * 65535 / input_length);
          
    check_newpts (this, pts, PTS_AUDIO, 0);
    
    /* Each packet of AAC is made up of several AAC frames preceded by a
     * header defining the size of the frames */
    if(this->audio_stream->buf_type == BUF_AUDIO_AAC) {
      int i, frames, *sizes;
      
      /* Upper 4 bits of second byte is frame count */
      frames = (stream_read_word(this) & 0xf0) >> 4;
      
      /* 2 bytes per frame size */
      sizes = xine_xmalloc(frames*sizeof(int));
      for(i = 0; i < frames; i++)
        sizes[i] = stream_read_word(this);
        
      for(i = 0; i < frames; i++) {
        if(_x_demux_read_send_data(this->audio_fifo, this->input, sizes[i], pts, 
             this->audio_stream->buf_type, 0, normpos, 
             input_time, this->duration, 0) < 0) {

          xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
                  "demux_real: failed to read AAC frame\n");

          free(sizes);
          this->status = DEMUX_FINISHED;
          return this->status;
        }
        
        pts = 0; /* Only set pts on first frame */
      }
        
      free(sizes);
    } else {
      if(_x_demux_read_send_data(this->audio_fifo, this->input, size, pts, 
           this->audio_stream->buf_type, 0, normpos,
           input_time, this->duration, 0) < 0) {

        xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, 
                "demux_real: failed to read audio chunk\n");

        this->status = DEMUX_FINISHED;
        return this->status;
      }
      
      /* FIXME: dp->flags = (flags & 0x2) ? 0x10 : 0; */
    }
  
  } else {

    /* discard */
    lprintf ("chunk not detected; discarding.\n");

discard:
    this->input->seek(this->input, size, SEEK_CUR);

  }

#if 0

  this->current_data_chunk_packet_count--;

  /* check if it's time to reload */
  if (!this->current_data_chunk_packet_count &&
      this->next_data_chunk_offset) {
    char            preamble[PREAMBLE_SIZE];
    unsigned char   data_chunk_header[DATA_CHUNK_HEADER_SIZE];

    /* seek to the next DATA chunk offset */
    this->input->seek(this->input, this->next_data_chunk_offset, SEEK_SET);

    /* load the DATA chunk preamble */
    if (this->input->read(this->input, preamble, PREAMBLE_SIZE) !=
      PREAMBLE_SIZE) {
      this->status = DEMUX_FINISHED;
      return this->status;
    }

    /* load the rest of the DATA chunk header */
    if (this->input->read(this->input, data_chunk_header,
      DATA_CHUNK_HEADER_SIZE) != DATA_CHUNK_HEADER_SIZE) {
      this->status = DEMUX_FINISHED;
      return this->status;
    }
    lprintf ("**** found next DATA tag\n");
    this->current_data_chunk_packet_count = _X_BE_32(&data_chunk_header[2]);
    this->next_data_chunk_offset = _X_BE_32(&data_chunk_header[6]);
  }

  if (!this->current_data_chunk_packet_count) {
    this->status = DEMUX_FINISHED;
    return this->status;
  }

#endif

  return this->status;
}

static void demux_real_send_headers(demux_plugin_t *this_gen) {

  demux_real_t *this = (demux_real_t *) this_gen;

  this->video_fifo = this->stream->video_fifo;
  this->audio_fifo = this->stream->audio_fifo;

  this->status = DEMUX_OK;

  this->last_pts[0]   = 0;
  this->last_pts[1]   = 0;

  this->avg_bitrate   = 1;


  /* send start buffers */
  _x_demux_control_start(this->stream);

  /* send init info to decoders */

  this->input->seek (this->input, 0, SEEK_SET);

  _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, 0);
  _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, 0);

  if( !this->reference_mode ) {
    real_parse_headers (this);
  } else {
    if ((this->input->get_capabilities (this->input) & INPUT_CAP_SEEKABLE) != 0)
      this->input->seek (this->input, 0, SEEK_SET);
  }
}

static int demux_real_seek (demux_plugin_t *this_gen,
                             off_t start_pos, int start_time, int playing) {

  demux_real_t       *this = (demux_real_t *) this_gen;
  real_index_entry_t *index, *other_index = NULL;
  int                 i = 0, entries;
  
  lprintf("seek start_pos=%d, start_time=%d, playing=%d\n",
          (int)start_pos, start_time, playing);

  if((this->input->get_capabilities(this->input) & INPUT_CAP_SEEKABLE) &&
     ((this->audio_stream && this->audio_stream->index) ||
      (this->video_stream && this->video_stream->index))) {
      
    start_pos = (off_t) ( (double) start_pos / 65535 *
                this->input->get_length (this->input) );

    /* video index has priority over audio index */
    if(this->video_stream && this->video_stream->index) {
      index = this->video_stream->index;
      entries = this->video_stream->index_entries;
      if(this->audio_stream)
        other_index = this->audio_stream->index;

      /* when seeking by video index the first audio chunk won't necesserily
       * be a keyframe which would upset the decoder */
      this->audio_need_keyframe = 1;
    } else {
      index = this->audio_stream->index;
      entries = this->audio_stream->index_entries;
      if(this->video_stream)
        other_index = this->video_stream->index;
    }

    /* FIXME: binary search would be quicker */
    if(start_pos) {
      while((i < entries - 1) && (index[i+1].offset < start_pos))
        i++;
    } else if(start_time) {
      while((i < entries - 1) && (index[i+1].timestamp < start_time))
        i++;
    }

    /* make sure we don't skip past audio/video at start of file */
    if((i == 0) && other_index && (other_index[0].offset < index[0].offset))
      index = other_index;

    this->input->seek(this->input, index[i].offset, SEEK_SET);

    if(playing) {
      this->buf_flag_seek = 1;
      _x_demux_flush_engine(this->stream);
    }
  }
  else if (!playing && this->input->seek_time != NULL) {
    /* RTSP supports only time based seek */
    if (start_pos && !start_time)
      start_time = (int64_t) this->duration * start_pos / 65535;
      
    this->input->seek_time(this->input, start_time, SEEK_SET);
  }

  this->send_newpts     = 1;
  this->old_seqnum      = -1;
  this->fragment_size   = 0;
  
  this->next_ts         = 0;
  this->next_seq        = 0;

  this->status          = DEMUX_OK;

  return this->status;
}

static void demux_real_dispose (demux_plugin_t *this_gen) {
  demux_real_t *this = (demux_real_t *) this_gen;
  int i;

  for(i = 0; i < this->num_video_streams; i++) {
    real_free_mdpr(this->video_streams[i].mdpr);
    if(this->video_streams[i].index)
      free(this->video_streams[i].index);
  }
  
  for(i = 0; i < this->num_audio_streams; i++) {
    real_free_mdpr(this->audio_streams[i].mdpr);
    if(this->audio_streams[i].index)
      free(this->audio_streams[i].index);
  }
  
  if(this->fragment_tab)
    free(this->fragment_tab);
  
  free(this);
}

static int demux_real_get_status (demux_plugin_t *this_gen) {
  demux_real_t *this = (demux_real_t *) this_gen;

  return this->status;
}

static int demux_real_get_stream_length (demux_plugin_t *this_gen) {
  demux_real_t *this = (demux_real_t *) this_gen;

  /* duration is stored in the file as milliseconds */
  return this->duration;
}

static uint32_t demux_real_get_capabilities(demux_plugin_t *this_gen) {
  return DEMUX_CAP_NOCAP;
}

static int demux_real_get_optional_data(demux_plugin_t *this_gen,
					void *data, int data_type) {
  return DEMUX_OPTIONAL_UNSUPPORTED;
}

/* help function to discover stream type. returns:
 *  0 if not known.
 *  1 if normal stream.
 *  2 if reference stream.
 */
static int real_check_stream_type(uint8_t *buf, int len)
{
  if ((buf[0] == 0x2e)
      && (buf[1] == 'R')
      && (buf[2] == 'M')
      && (buf[3] == 'F'))
    return 1;

  buf[len] = '\0';
  if( strstr(buf,"pnm://") || strstr(buf,"rtsp://") || strstr(buf,"<smil>") ||
      !strncmp(buf,"http://",7) )
    return 2;

  return 0;
}

static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *stream,
                                    input_plugin_t *input) {

  demux_real_t   *this;
  uint8_t buf[1024+1];
  int len, stream_type=0;

  switch (stream->content_detection_method) {

  case METHOD_BY_CONTENT:{

    if (! (len = _x_demux_read_header(input, buf, 1024)) )
      return NULL;

    lprintf ("read 4 bytes: %02x %02x %02x %02x\n",
             buf[0], buf[1], buf[2], buf[3]);

    if (!(stream_type = real_check_stream_type(buf,len)))
      return NULL;
  }

  lprintf ("by content accepted.\n");
  break;

  case METHOD_BY_EXTENSION: {
    const char *extensions, *mrl;

    mrl = input->get_mrl (input);
    extensions = class_gen->get_extensions (class_gen);

    lprintf ("by extension '%s'\n", mrl);

    if (!_x_demux_check_extension (mrl, extensions)) {
      return NULL;
    }
    lprintf ("by extension accepted.\n");
  }

  break;

  case METHOD_EXPLICIT:
    break;

  default:
    return NULL;
  }


  this         = xine_xmalloc (sizeof (demux_real_t));
  this->stream = stream;
  this->input  = input;


  /* discover stream type */
  if(!stream_type)
    if ( (len = _x_demux_read_header(this->input, buf, 1024)) )
      stream_type = real_check_stream_type(buf,len);

  if(stream_type == 2){
    this->reference_mode = 1;
    lprintf("reference stream detected\n");
  }else
    this->reference_mode = 0;


  this->demux_plugin.send_headers      = demux_real_send_headers;
  this->demux_plugin.send_chunk        = demux_real_send_chunk;
  this->demux_plugin.seek              = demux_real_seek;
  this->demux_plugin.dispose           = demux_real_dispose;
  this->demux_plugin.get_status        = demux_real_get_status;
  this->demux_plugin.get_stream_length = demux_real_get_stream_length;
  this->demux_plugin.get_capabilities  = demux_real_get_capabilities;
  this->demux_plugin.get_optional_data = demux_real_get_optional_data;
  this->demux_plugin.demux_class       = class_gen;

  return &this->demux_plugin;
}

static const char *get_description (demux_class_t *this_gen) {
  return "RealMedia file demux plugin";
}

static const char *get_identifier (demux_class_t *this_gen) {
  return "Real";
}

static const char *get_extensions (demux_class_t *this_gen) {
  return "rm rmvb ram";
}

static const char *get_mimetypes (demux_class_t *this_gen) {
  return "audio/x-pn-realaudio: ra, rm, ram: Real Media file;"
         "audio/x-pn-realaudio-plugin: rpm: Real Media plugin file;"
         "audio/x-real-audio: ra, rm, ram: Real Media file;"
         "application/vnd.rn-realmedia: ra, rm, ram: Real Media file;"; 
}

static void class_dispose (demux_class_t *this_gen) {
  demux_real_class_t *this = (demux_real_class_t *) this_gen;

  free (this);
}

static void *init_class (xine_t *xine, void *data) {
  demux_real_class_t     *this;

  this = xine_xmalloc (sizeof (demux_real_class_t));

  this->demux_class.open_plugin     = open_plugin;
  this->demux_class.get_description = get_description;
  this->demux_class.get_identifier  = get_identifier;
  this->demux_class.get_mimetypes   = get_mimetypes;
  this->demux_class.get_extensions  = get_extensions;
  this->demux_class.dispose         = class_dispose;

  return this;
}

/*
 * exported plugin catalog entry
 */
static const demuxer_info_t demux_info_real = {
  10                       /* priority */
};

const plugin_info_t xine_plugin_info[] EXPORTED = {
  /* type, API, "name", version, special_info, init_function */
  { PLUGIN_DEMUX, 26, "real", XINE_VERSION_CODE, &demux_info_real, init_class },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};
