summaryrefslogtreecommitdiff
path: root/src/demuxers/demux_matroska.c
blob: 18ea8acf58235be0ec5a96824afc6104ead676f8 (plain)
1
/*
 * Copyright (C) 2000-2003 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: demux_matroska.c,v 1.7 2004/01/10 20:59:15 tmattern Exp $
 *
 * demultiplexer for matroska streams
 *
 * TODO:
 *   memory leaks
 *   seeking
 *   subtitles
 *   more codecs
 *   metadata
 *
 */

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

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

#define LOG_MODULE "demux_matroska"
#define LOG_VERBOSE

#define LOG

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

#include "ebml.h"
#include "matroska.h"

#define NUM_PREVIEW_BUFFERS      10

#define MAX_STREAMS             128
#define MAX_FRAMES               32

#define WRAP_THRESHOLD        90000

#define INIT_STD_VIDEO            0
#define INIT_STD_AUDIO            1
#define INIT_VORBIS               2


typedef struct {

  demux_plugin_t       demux_plugin;

  xine_stream_t       *stream;

  input_plugin_t      *input;

  int                  status;

  ebml_parser_t       *ebml;

  /* segment element */
  ebml_elem_t          segment;
  uint64_t             timecode_scale;
  int                  duration;            /* in millis */
  int                  preview_sent;
  int                  preview_mode;

  /* meta seek info */
  off_t                seekhead_pos;
  off_t                info_pos;
  off_t                tracks_pos;
  off_t                chapters_pos;
  off_t                cues_pos;
  off_t                attachments_pos;
  off_t                tags_pos;
  int                  has_seekhead;
  int                  seekhead_handled;

  /* tracks */
  int                  num_tracks;
  int                  num_video_tracks;
  int                  num_audio_tracks;
  int                  num_sub_tracks;

  matroska_track_t    *tracks[MAX_STREAMS];

  /* block */
  uint8_t             *block_data;
  int                  block_data_size;

  /* current tracks */
  matroska_track_t    *video_track;   /* to remove */
  matroska_track_t    *audio_track;   /* to remove */
  matroska_track_t    *sub_track;     /* to remove */
  
  int                  send_newpts;
  int                  buf_flag_seek;
} demux_matroska_t ;

typedef struct {

  demux_class_t     demux_class;

  /* class-wide, global variables here */

  xine_t           *xine;

} demux_matroska_class_t;


static void check_newpts (demux_matroska_t *this, int64_t pts,
                          matroska_track_t *track) {
  int64_t diff;

  if ((track->track_type == MATROSKA_TRACK_VIDEO) ||
      (track->track_type == MATROSKA_TRACK_AUDIO)) {
  
    diff = pts - track->last_pts;

    if (pts && (this->send_newpts || (track->last_pts && abs(diff)>WRAP_THRESHOLD)) ) {
      int i;

      lprintf ("sending newpts %lld, diff %lld, track %d\n", pts, diff, track->track_num);

      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;
      for (i = 0; i < this->num_tracks; i++) {
        this->tracks[i]->last_pts = 0;
      }
    } else {
  #ifdef LOG
      if (pts)
        lprintf ("diff %lld, track %d\n", diff, track->track_num);
  #endif
    }

    if (pts)
      track->last_pts = pts;
  }
}


static int parse_info(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  double duration = 0.0; /* in matroska unit */
  
  while (next_level == 2) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_I_TIMECODESCALE:
        lprintf("timecode_scale\n");
        if (!ebml_read_uint(ebml, &elem, &this->timecode_scale))
          return 0;
        break;
      case MATROSKA_ID_I_DURATION: {
        
        lprintf("duration\n");
        if (!ebml_read_float(ebml, &elem, &duration))
          return 0;
      }
      break;
      
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  if (this->timecode_scale == 0) {
    this->timecode_scale = 1000000;
  }
  this->duration = (int)(duration * (double)this->timecode_scale / 1000000.0);
  lprintf("timecode_scale: %lld\n", this->timecode_scale);
  lprintf("duration: %d\n", this->duration);
  return 1;
}


static int parse_video_track (demux_matroska_t *this, matroska_video_track_t *vt) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 4;
  uint64_t val;

  while (next_level == 4) {
    ebml_elem_t elem;

    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_TV_FLAGINTERLACED:
        lprintf("MATROSKA_ID_TV_FLAGINTERLACED\n");
        if (!ebml_read_uint(ebml, &elem, &val))
          return 0;
        vt->flag_interlaced = val;
        break;
      case MATROSKA_ID_TV_PIXELWIDTH:
        lprintf("MATROSKA_ID_TV_PIXELWIDTH\n");
        if (!ebml_read_uint(ebml, &elem, &val))
          return 0;
        vt->pixel_witdh = val;
        break;
      case MATROSKA_ID_TV_PIXELHEIGHT:
        lprintf("MATROSKA_ID_TV_PIXELHEIGHT\n");
        if (!ebml_read_uint(ebml, &elem, &val))
          return 0;
        vt->pixel_height = val;
        break;
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static int parse_audio_track (demux_matroska_t *this, matroska_audio_track_t *at) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 4;

  while (next_level == 4) {
    ebml_elem_t elem;
    uint64_t    val;
    double      fval;

    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_TA_SAMPLINGFREQUENCY:
        lprintf("MATROSKA_ID_TA_SAMPLINGFREQUENCY\n");
        if (!ebml_read_float(ebml, &elem, &fval))
          return 0;
        at->sampling_freq = (int)fval;
        break;
      case MATROSKA_ID_TA_OUTPUTSAMPLINGFREQUENCY:
        lprintf("MATROSKA_ID_TA_OUTPUTSAMPLINGFREQUENCY\n");
        if (!ebml_read_float(ebml, &elem, &fval))
          return 0;
        at->output_sampling_freq = (int)fval;
        break;
      case MATROSKA_ID_TA_CHANNELS:
        lprintf("MATROSKA_ID_TA_CHANNELS\n");
        if (!ebml_read_uint(ebml, &elem, &val))
          return 0;
        at->channels = val;
        break;
      case MATROSKA_ID_TA_BITDEPTH:
        lprintf("MATROSKA_ID_TA_BITDEPTH\n");
        if (!ebml_read_uint(ebml, &elem, &val))
          return 0;
        at->bits_per_sample = val;
        break;
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static void init_codec_video(demux_matroska_t *this, matroska_track_t *track) {
  buf_element_t *buf;

  buf = track->fifo->buffer_pool_alloc (track->fifo);
  buf->type = track->buf_type;

  if (track->codec_private_len > buf->max_size) {
    buf->size = buf->max_size;
  } else {
    buf->size = track->codec_private_len;
  }

  if (buf->size)
    xine_fast_memcpy (buf->content, track->codec_private, buf->size);
  else
    buf->content = NULL;

  buf->decoder_flags = BUF_FLAG_HEADER | BUF_FLAG_STDHEADER;
  buf->type          = track->buf_type;
  buf->pts           = 0;
  track->fifo->put (track->fifo, buf);
}


static void init_codec_audio(demux_matroska_t *this, matroska_track_t *track) {
  buf_element_t *buf;

  buf = track->fifo->buffer_pool_alloc (track->fifo);

  /* default param */
  buf->decoder_info[0] = 0;
  buf->decoder_info[1] = 44100;
  buf->decoder_info[2] = 16;
  buf->decoder_info[3] = 2;
  /* track param */
  if (track->audio_track) {
    if (track->audio_track->sampling_freq)
      buf->decoder_info[1] = track->audio_track->sampling_freq;
    if (track->audio_track->bits_per_sample)
      buf->decoder_info[2] = track->audio_track->bits_per_sample;
    if (track->audio_track->channels)
      buf->decoder_info[3] = track->audio_track->channels;
  }
  lprintf("%d Hz, %d bits, %d channels\n", buf->decoder_info[1],
          buf->decoder_info[2], buf->decoder_info[3]);

  if (track->codec_private_len > buf->max_size) {
    buf->size = buf->max_size;
  } else {
    buf->size = track->codec_private_len;
  }

  if (buf->size)
    xine_fast_memcpy (buf->content, track->codec_private, buf->size);
  else
    buf->content = NULL;

  buf->decoder_flags = BUF_FLAG_HEADER | BUF_FLAG_STDHEADER;
  buf->type          = track->buf_type;
  buf->pts           = 0;
  track->fifo->put (track->fifo, buf);
}


static void init_codec_vorbis(demux_matroska_t *this, matroska_track_t *track) {
  buf_element_t *buf;
  uint8_t nb_lace;
  int frame[3];
  int i;
  uint8_t *data;

  nb_lace = track->codec_private[0];
  if (nb_lace != 2)
    return;

  frame[0] = track->codec_private[1];
  frame[1] = track->codec_private[2];
  frame[2] = track->codec_private_len - frame[0] - frame[1] - 3;

  data = track->codec_private + 3;
  for (i = 0; i < 3; i++) {
    buf = track->fifo->buffer_pool_alloc (track->fifo);
    buf->decoder_flags = BUF_FLAG_HEADER;
    buf->type          = track->buf_type;
    buf->pts           = 0;
    buf->size          = frame[i];

    xine_fast_memcpy (buf->content, data, buf->size);
    data += buf->size;

    track->fifo->put (track->fifo, buf);
  }
}


static int parse_track_entry(demux_matroska_t *this, matroska_track_t *track) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 3;
  
  while (next_level == 3) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_TR_NUMBER: {
        uint64_t num;
        lprintf("TrackNumber\n");
        if (!ebml_read_uint(ebml, &elem, &num))
          return 0;
        track->track_num = num;
      }
      break;
      
      case MATROSKA_ID_TR_TYPE: {
        uint64_t num;
        lprintf("TrackType\n");
        if (!ebml_read_uint(ebml, &elem, &num))
          return 0;
        track->track_type = num;
      }
      break;
      
      case MATROSKA_ID_TR_CODECID: {
        char *codec_id = malloc (elem.len + 1);
        lprintf("CodecID\n");
        if (!ebml_read_ascii(ebml, &elem, codec_id))
          return 0;
        codec_id[elem.len] = '\0';
        track->codec_id = codec_id;
      }
      break;
        
      case MATROSKA_ID_TR_CODECPRIVATE: {
        char *codec_private = malloc (elem.len);
        lprintf("CodecPrivate\n");
        if (!ebml_read_binary(ebml, &elem, codec_private))
          return 0;
        track->codec_private = codec_private;
        track->codec_private_len = elem.len;
      }
      break;
        
      case MATROSKA_ID_TR_LANGUAGE: {
        char *language = malloc (elem.len + 1);
        lprintf("Language\n");
        if (!ebml_read_ascii(ebml, &elem, language))
          return 0;
        language[elem.len] = '\0';
        track->language = language;
      }
      break;
      
      case MATROSKA_ID_TV:
        lprintf("Video\n");
        if (track->video_track)
          return 1;
        track->video_track = (matroska_video_track_t *)xine_xmalloc(sizeof(matroska_video_track_t));
        if (!ebml_read_master (ebml, &elem))
          return 0;
        if (!parse_video_track(this, track->video_track))
          return 0;
      break;
      
      case MATROSKA_ID_TA:
        lprintf("Audio\n");
        if (track->audio_track)
          return 1;
        track->audio_track = (matroska_audio_track_t *)xine_xmalloc(sizeof(matroska_audio_track_t));
        if (!ebml_read_master (ebml, &elem))
          return 0;
        if (!parse_audio_track(this, track->audio_track))
          return 0;
      break;
        
      case MATROSKA_ID_TR_FLAGDEFAULT: {
        uint64_t val;
        
        lprintf("Default\n");
        if (!ebml_read_uint(ebml, &elem, &val))
          return 0;
        track->default_flag = (int)val;
      }
      break;

      case MATROSKA_ID_TR_DEFAULTDURATION: {
        uint64_t val;

        if (!ebml_read_uint(ebml, &elem, &val))
          return 0;
        track->default_duration = val;
        lprintf("Default Duration: %lld\n", track->default_duration);
      }
      break;

      case MATROSKA_ID_TR_UID:
      case MATROSKA_ID_TR_FLAGENABLED:
      case MATROSKA_ID_TR_FLAGLACING:
      case MATROSKA_ID_TR_MINCACHE:
      case MATROSKA_ID_TR_MAXCACHE:
      case MATROSKA_ID_TR_TIMECODESCALE:
      case MATROSKA_ID_TR_NAME:
      case MATROSKA_ID_TR_CODECNAME:
      case MATROSKA_ID_TR_CODECSETTINGS:
      case MATROSKA_ID_TR_CODECINFOURL:
      case MATROSKA_ID_TR_CODECDOWNLOADURL:
      case MATROSKA_ID_TR_CODECDECODEALL:
      case MATROSKA_ID_TR_OVERLAY:
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem)) {
          return 0;
        }
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  
  xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
          "demux_matroska: Track %d, %s %s\n",
          track->track_num,
          (track->codec_id ? track->codec_id : ""),
          (track->language ? track->language : ""));

  if (track->codec_id) {
    int init_mode = INIT_STD_VIDEO;

    if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_VFW_FOURCC)) {
      xine_bmiheader *bih;

      lprintf("MATROSKA_CODEC_ID_V_VFW_FOURCC\n");
      bih = (xine_bmiheader*)track->codec_private;
      _x_bmiheader_le2me(bih);

      track->buf_type = _x_fourcc_to_buf_video(bih->biCompression);
      init_mode = INIT_STD_VIDEO;

    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_UNCOMPRESSED)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_SP)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_ASP)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG4_AP)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MSMPEG4V3)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG1)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MPEG2)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_REAL_RV10)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_REAL_RV20)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_REAL_RV30)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_REAL_RV40)) {
    
      lprintf("MATROSKA_CODEC_ID_V_REAL_RV40\n");
      track->buf_type = BUF_VIDEO_RV40;
      init_mode = INIT_STD_VIDEO;

    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_V_MJPEG)) {
    } else if ((!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L1)) ||
               (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L2)) ||
               (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_MPEG1_L3))) {
      lprintf("MATROSKA_CODEC_ID_A_MPEG1\n");
      track->buf_type = BUF_AUDIO_MPEG;
      init_mode = INIT_STD_AUDIO;

    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_PCM_INT_BE)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_PCM_INT_LE)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_PCM_FLOAT)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_AC3)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_DTS)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_VORBIS)) {

      lprintf("MATROSKA_CODEC_ID_A_VORBIS\n");
      track->buf_type = BUF_AUDIO_VORBIS;
      init_mode = INIT_VORBIS;

    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_ACM)) {
    } else if (!strncmp(track->codec_id, MATROSKA_CODEC_ID_A_AAC,
                        sizeof(MATROSKA_CODEC_ID_A_AAC - 1))) {
      lprintf("MATROSKA_CODEC_ID_A_AAC\n");
      track->buf_type = BUF_AUDIO_AAC;
      init_mode = INIT_STD_AUDIO;
      
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_REAL_14_4)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_REAL_28_8)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_REAL_COOK)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_REAL_SIPR)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_REAL_RALF)) {
    } else if (!strcmp(track->codec_id, MATROSKA_CODEC_ID_A_REAL_ATRC)) {
    } else {
      lprintf("unknown codec\n");
    }

    if (track->buf_type) {

      switch(track->track_type) {
        case MATROSKA_TRACK_VIDEO:
          track->fifo = this->stream->video_fifo;
          track->buf_type |= this->num_video_tracks;
          this->num_video_tracks++;
          break;
        case MATROSKA_TRACK_AUDIO:
          track->fifo = this->stream->audio_fifo;
          track->buf_type |= this->num_audio_tracks;
          this->num_audio_tracks++;
          break;
        case MATROSKA_TRACK_SUBTITLE:
          track->fifo = this->stream->video_fifo;
          track->buf_type |= this->num_sub_tracks;
          this->num_sub_tracks++;
          break;
        case MATROSKA_TRACK_COMPLEX:
        case MATROSKA_TRACK_LOGO:
        case MATROSKA_TRACK_CONTROL:
          break;
      }

      switch (init_mode) {
        case INIT_STD_VIDEO:
          init_codec_video(this, track);
        break;

        case INIT_STD_AUDIO:
          init_codec_audio(this, track);
        break;

        case INIT_VORBIS:
          init_codec_vorbis(this, track);
        break;
      }
    }
  }
  
  return 1;
}


static int parse_tracks(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  
  while (next_level == 2) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_TR_ENTRY: {
        this->tracks[this->num_tracks] = xine_xmalloc(sizeof(matroska_track_t));
        lprintf("TrackEntry\n");
        if (!ebml_read_master (ebml, &elem))
          return 0;
        if (!parse_track_entry(this, this->tracks[this->num_tracks]))
          return 0;
        this->num_tracks++;
      }
      break;
      
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static int parse_chapters(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  
  while (next_level == 2) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static int parse_cues(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  
  while (next_level == 2) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static int parse_attachments(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  
  while (next_level == 2) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static int parse_tags(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  
  while (next_level == 2) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}

static void alloc_block_data (demux_matroska_t *this, int len) {
  /* memory management */
  if (this->block_data_size < len) {
    if (this->block_data)
      this->block_data = realloc(this->block_data, len);
    else
      this->block_data = malloc(len);
    this->block_data_size = len;
  }
}

static void handle_realvideo (demux_matroska_t *this, matroska_track_t *track,
                              uint8_t *data, int len, int64_t pts,
                              off_t input_pos, off_t input_length,
                              int input_time, int duration) {
  int chunks;
  int chunk_tab_size;

  chunks = data[0];
  chunk_tab_size = (chunks + 1) * 8;

  lprintf("chunks: %d, chunk_tab_size: %d\n", chunks, chunk_tab_size);

  _x_demux_send_data(track->fifo,
                     data + chunk_tab_size + 1,
                     len - chunk_tab_size - 1,
                     pts, track->buf_type, 0,
                     0, 0, 0,
                     this->duration, 0);

  /* sends the fragment table */
  {
    buf_element_t *buf;
    int64_t frame_duration;
    
    frame_duration = (int64_t)track->default_duration *
                     (int64_t)90 / (int64_t)1000000;

    buf = track->fifo->buffer_pool_alloc(track->fifo);

    buf->decoder_flags = BUF_FLAG_SPECIAL;
    buf->decoder_info[1] = BUF_SPECIAL_RV_CHUNK_TABLE;
    buf->decoder_info[2] = chunks;
    buf->decoder_info_ptr[2] = buf->content;
    buf->decoder_info[3] = frame_duration;
    buf->size = chunk_tab_size;
    buf->type = track->buf_type;

    xine_fast_memcpy(buf->decoder_info_ptr[2], data + 1, chunk_tab_size);

    track->fifo->put(track->fifo, buf);
  }


}


static int parse_ebml_uint(demux_matroska_t *this, uint8_t *data, uint64_t *num) {
  uint8_t mask = 0x80;
  int size = 1;
  int i;

  /* compute the size of the "data len" (1-8 bytes) */
  while (size <= 8 && !(data[0] & mask)) {
    size++;
    mask >>= 1;
  }
  if (size > 8) {
    off_t pos = this->input->get_current_pos(this->input);
    xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
            "demux_matroska: Invalid Track Number at position %llu\n", pos);
    return 0;
  }

  *num = data[0];
  *num &= mask - 1;

  for (i = 1; i < size; i++) {
    *num = (*num << 8) | data[i];
  }
  return size;
}


static int parse_ebml_sint(demux_matroska_t *this, uint8_t *data, int64_t *num) {
  uint64_t unum;
  int size;

  size = parse_ebml_uint(this, data, &unum);
  if (!size)
    return 0;

  /* formula taken from gstreamer demuxer */
  if (unum == -1)
    *num = -1;
  else
    *num = unum - ((1 << ((7 * size) - 1)) - 1);
  
  return size;
}

static int find_track_by_id(demux_matroska_t *this, int track_num,
                            matroska_track_t **track) {
  int i;

  *track = NULL;
  for (i = 0; i < this->num_tracks; i++) {
    if (this->tracks[i]->track_num == track_num) {
      *track = this->tracks[i];
      return 1;
    }
  }
  return 0;
}


static int read_block_data (demux_matroska_t *this, int len) {
  alloc_block_data(this, len);

  /* block datas */
  if (this->input->read(this->input, this->block_data, len) != len) {
    off_t pos = this->input->get_current_pos(this->input);
    xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
            "demux_matroska: read error at position %llu\n", pos);
    return 0;
  }
  return 1;
}

static int parse_block (demux_matroska_t *this, uint64_t block_size,
                        uint64_t timecode, uint64_t duration) {
  matroska_track_t *track;
  int64_t           track_num;
  int               timecode_diff;
  uint8_t          *data;
  uint8_t           flags;
  int               gap;
  int               lacing;
  int               num_len;
  
  if (!read_block_data(this, block_size))
    return 0;

  data = this->block_data;
  if (!(num_len = parse_ebml_uint(this, data, &track_num)))
    return 0;
  data += num_len;
    
  timecode_diff = (int)BE_16(data);
  data += 2;

  flags = *data;
  data += 1;
  
  lprintf("track_num: %lld, timecode_diff: %d, flags: 0x%x\n", track_num, timecode_diff, flags);

  gap = flags & 1;
  lacing = (flags >> 1) & 0x3;

  if (!find_track_by_id(this, (int)track_num, &track)) {
     xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
             "demux_matroska: invalid track id: %lld\n", track_num);
     return 0;
  }

  {
    int64_t pts;
    int decoder_flags = 0;
    off_t input_pos, input_len;

    pts = ((int64_t)timecode + timecode_diff) *
          (int64_t)this->timecode_scale * (int64_t)90 /
          (int64_t)1000000;
    lprintf("pts: %lld\n", pts);

    check_newpts(this, pts, track);

    if (this->preview_mode) {
      this->preview_sent++;
      decoder_flags |= BUF_FLAG_PREVIEW;
    }
    
    input_pos = this->input->get_current_pos(this->input);
    input_len = this->input->get_length(this->input);
    
    if (lacing == MATROSKA_NO_LACING) {
      int block_size_left;
      lprintf("no lacing\n");

      block_size_left = (this->block_data + block_size) - data;
      lprintf("size: %d, block_size: %lld\n", block_size_left, block_size);
      if (track->buf_type == BUF_VIDEO_RV40) {
        handle_realvideo(this, track, data, block_size_left, pts,
                         input_pos, input_len, pts / 90, this->duration);
      } else {
        _x_demux_send_data(track->fifo, data, block_size_left,
                           pts, track->buf_type, decoder_flags,
                           input_pos, input_len, pts / 90,
                           this->duration, 0);
      }
    } else {
    
      int block_size_left;
      uint8_t lace_num;
      int frame[MAX_FRAMES];
      int i;

      /* number of laced frames */
      lace_num = *data;
      data++;
      lprintf("lace_num: %d\n", lace_num);
      if ((lace_num + 1) > MAX_FRAMES) {
        xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
                "demux_matroska: too many frames: %d\n", lace_num);
        return 0;
      }
      block_size_left = this->block_data + block_size - data;

      switch (lacing) {
        case MATROSKA_XIPH_LACING: {

          lprintf("xiph lacing\n");

          /* size of each frame */
          for (i = 0; i < lace_num; i++) {
            int size = 0;
            while (*data == 255) {
              size += *data;
              data++; block_size_left--;
            }
            size += *data;
            data++; block_size_left--;
            frame[i] = size;
            block_size_left -= size;
          }

          /* last frame */
          frame[lace_num] = block_size_left;
        }
        break;

        case MATROSKA_FIXED_SIZE_LACING:
          lprintf("fixed size lacing\n");
          for (i = 0; i < lace_num; i++) {
            frame[i] = block_size / (lace_num + 1);
            block_size_left -= frame[i];
          }
          frame[lace_num] = block_size_left;
          break;

        case MATROSKA_EBML_LACING: {
          int64_t tmp;

          lprintf("ebml lacing\n");

          /* size of each frame */
          if (!(num_len = parse_ebml_uint(this, data, &tmp)))
            return 0;
          data += num_len; block_size_left -= num_len;
          frame[0] = (int) tmp;
          lprintf("first frame len: %d\n", frame[0]);
          block_size_left -= frame[0];

          for (i = 1; i < lace_num; i++) {
            if (!(num_len = parse_ebml_sint(this, data, &tmp)))
              return 0;

            data += num_len; block_size_left -= num_len;
            frame[i] = frame[i-1] + tmp;
            block_size_left -= frame[i];
          }

          /* last frame */
          frame[lace_num] = block_size_left;
        }
        break;
        default:
          xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
                  "demux_matroska: invalid lacing: %d\n", lacing);
          return 0;
      }
      /* send each frame to the decoder */
      for (i = 0; i <= lace_num; i++) {
        _x_demux_send_data(track->fifo, data, frame[i],
                           pts, track->buf_type, decoder_flags,
                           input_pos, input_len, pts / 90,
                           0, 0);
        data += frame[i];
        pts = 0;
      }
    }
  }
  return 1;
}

static int parse_block_group(demux_matroska_t *this,
                             uint64_t timecode, uint64_t duration) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 3;

  while (next_level == 3) {
    ebml_elem_t elem;

    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_CL_BLOCK:
        lprintf("block\n");
        if (!parse_block(this, elem.len, timecode, duration))
          return 0;
        break;
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}

static int parse_cluster(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  uint64_t timecode = 0;
  uint64_t duration = 0;

  while (next_level == 2) {
    ebml_elem_t elem;

    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_CL_TIMECODE:
        lprintf("timecode\n");
        if (!ebml_read_uint(ebml, &elem, &timecode))
          return 0;
        break;
      case MATROSKA_ID_CL_DURATION:
        lprintf("duration\n");
        if (!ebml_read_uint(ebml, &elem, &duration))
          return 0;
        break;
      case MATROSKA_ID_CL_BLOCKGROUP:
        lprintf("blockgroup\n");
        if (!ebml_read_master (ebml, &elem))
          return 0;
        if (!parse_block_group(this, timecode, duration))
          return 0;
        break;
      case MATROSKA_ID_CL_BLOCK:
        lprintf("block\n");
        if (!ebml_skip(ebml, &elem))
          return 0;
        break;
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static int parse_seek_entry(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 3;
  int has_id = 0;
  int has_position = 0;
  uint64_t id, pos;
  
  while (next_level == 3) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_S_ID:
        lprintf("SeekID\n");
        if (!ebml_read_uint(ebml, &elem, &id))
          return 0;
        has_id = 1;
        break;
      case MATROSKA_ID_S_POSITION:
        lprintf("SeekPosition\n");
        if (!ebml_read_uint(ebml, &elem, &pos))
          return 0;
        has_position = 1;
        break;
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  if (has_id && has_position) {
    
    switch (id) {
      case MATROSKA_ID_INFO:
        lprintf("Seek Entry: Info: %lld\n", pos);
        this->info_pos = this->segment.start + pos;
        break;
      case MATROSKA_ID_SEEKHEAD:
        lprintf("Seek Entry: SeekHead: %lld\n", pos);
        this->seekhead_pos = this->segment.start + pos;
        break;
      case MATROSKA_ID_CLUSTER:
        lprintf("Seek Entry: Cluster: %lld\n", pos);
        break;
      case MATROSKA_ID_TRACKS:
        lprintf("Seek Entry: Tracks: %lld\n", pos);
        this->tracks_pos = this->segment.start + pos;
        break;
      case MATROSKA_ID_CUES:
        lprintf("Seek Entry: Cues: %lld\n", pos);
        this->cues_pos = this->segment.start + pos;
        break;
      case MATROSKA_ID_ATTACHMENTS:
        lprintf("Seek Entry: Attachements: %lld\n", pos);
        this->attachments_pos = this->segment.start + pos;
        break;
      case MATROSKA_ID_CHAPTERS:
        lprintf("Seek Entry: Chapters: %lld\n", pos);
        this->chapters_pos = this->segment.start + pos;
        break;
      default:
        lprintf("Unhandled Seek Entry ID: 0x%llx\n", id);
    }
    return 1;
  } else {
    lprintf("incomplete Seek Entry\n");
    return 1;
  }
}


static int parse_seekhead(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;
  int next_level = 2;
  
  this->has_seekhead = 1;
  
  while (next_level == 2) {
    ebml_elem_t elem;
    
    if (!ebml_read_elem_head(ebml, &elem))
      return 0;

    switch (elem.id) {
      case MATROSKA_ID_S_ENTRY:
        lprintf("Seek\n");
        if (!ebml_read_master (ebml, &elem))
          return 0;
        if (!parse_seek_entry(this))
          return 0;
        break;
      default:
        lprintf("Unhandled ID: 0x%x\n", elem.id);
        if (!ebml_skip(ebml, &elem))
          return 0;
    }
    next_level = ebml_get_next_level(ebml, &elem);
  }
  return 1;
}


static int parse_top_level_head(demux_matroska_t *this, int *next_level) {
  ebml_parser_t *ebml = this->ebml;
  ebml_elem_t elem;

  if (!ebml_read_elem_head(ebml, &elem))
    return 0;

  switch (elem.id) {
    case MATROSKA_ID_SEEKHEAD:
      lprintf("SeekHead\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_seekhead(this))
        return 0;
      this->has_seekhead = 1;
      break;
    case MATROSKA_ID_INFO:
      lprintf("Info\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_info(this))
        return 0;
      break;
    case MATROSKA_ID_TRACKS:
      lprintf("Tracks\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_tracks(this))
        return 0;
      break;
    case MATROSKA_ID_CHAPTERS:
      lprintf("Chapters\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_chapters(this))
        return 0;
      break;
    case MATROSKA_ID_CLUSTER:
      lprintf("Cluster\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      break;
    case MATROSKA_ID_CUES:
      lprintf("Cues\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_cues(this))
        return 0;
      break;
    case MATROSKA_ID_ATTACHMENTS:
      lprintf("Attachments\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_attachments(this))
        return 0;
      break;
    case MATROSKA_ID_TAGS:
      lprintf("Tags\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_tags(this))
        return 0;
      break;
      
    default:
      lprintf("Unhandled ID: 0x%x\n", elem.id);
      if (!ebml_skip(ebml, &elem))
        return 0;
  }
  if (next_level)
    *next_level = ebml_get_next_level(ebml, &elem);
  return 1;
}

static int parse_top_level(demux_matroska_t *this, int *next_level) {
  ebml_parser_t *ebml = this->ebml;
  ebml_elem_t elem;

  if (!ebml_read_elem_head(ebml, &elem))
    return 0;

  switch (elem.id) {
    case MATROSKA_ID_SEEKHEAD:
      lprintf("SeekHead\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      this->has_seekhead = 1;
      break;
    case MATROSKA_ID_INFO:
      lprintf("Info\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      break;
    case MATROSKA_ID_TRACKS:
      lprintf("Tracks\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      break;
    case MATROSKA_ID_CHAPTERS:
      lprintf("Chapters\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      break;
    case MATROSKA_ID_CLUSTER:
      lprintf("Cluster\n");
      if (!ebml_read_master (ebml, &elem))
        return 0;
      if (!parse_cluster(this))
        return 0;
      break;
    case MATROSKA_ID_CUES:
      lprintf("Cues\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      break;
    case MATROSKA_ID_ATTACHMENTS:
      lprintf("Attachments\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      break;
    case MATROSKA_ID_TAGS:
      lprintf("Tags\n");
      if (!ebml_skip(ebml, &elem))
        return 0;
      break;

    default:
      lprintf("Unhandled ID: 0x%x\n", elem.id);
      if (!ebml_skip(ebml, &elem))
        return 0;
  }
  if (next_level)
    *next_level = ebml_get_next_level(ebml, &elem);
  return 1;
}


static int parse_segment(demux_matroska_t *this) {
  ebml_parser_t *ebml = this->ebml;

  /* check segment id */
  if (!ebml_read_elem_head(ebml, &this->segment))
    return 0;

  if (this->segment.id == MATROSKA_ID_SEGMENT) {
    int next_level;
    
    lprintf("Segment\n");

    if (!ebml_read_master (ebml, &this->segment))
      return 0;
    
    next_level = 1;
    while (next_level == 1) {
      if (!parse_top_level_head(this, &next_level))
        return 0;
#if 0
      if (this->has_seekhead && !this->seekhead_handled) {
        if (this->seekhead_pos) {
          if (this->input->seek(this->input, this->seekhead_pos, SEEK_SET) < 0)
            return 0;
          this->seekhead_pos = 0;
        } else {
          /* parse all top level elements except clusters */
          if (this->info_pos) {
            if (this->input->seek(this->input, this->info_pos, SEEK_SET) < 0)
              return 0;
            if (!parse_top_level(this, &next_level))
              return 0;
          }
          if (this->tracks_pos) {
            if (this->input->seek(this->input, this->tracks_pos, SEEK_SET) < 0)
              return 0;
            if (!parse_top_level(this, &next_level))
              return 0;
          }
          if (this->chapters_pos) {
            if (this->input->seek(this->input, this->chapters_pos, SEEK_SET) < 0)
              return 0;
            if (!parse_top_level(this, &next_level))
              return 0;
          }
          if (this->cues_pos) {
            if (this->input->seek(this->input, this->cues_pos, SEEK_SET) < 0)
              return 0;
            if (!parse_top_level(this, &next_level))
              return 0;
          }
          if (this->attachments_pos) {
            if (this->input->seek(this->input, this->attachments_pos, SEEK_SET) < 0)
              return 0;
            if (!parse_top_level(this, &next_level))
              return 0;
          }
          if (this->tags_pos) {
            if (this->input->seek(this->input, this->tags_pos, SEEK_SET) < 0)
              return 0;
            if (!parse_top_level(this, &next_level))
              return 0;
          }
          /* this->seekhead_handled = 1; */
          return 1;
        }
      }
#endif
    }
    return 1;
  } else {
    /* not a segment */
    xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG,
            "demux_matroska: invalid segment\n");
    return 0;
  }
}

static int demux_matroska_send_chunk (demux_plugin_t *this_gen) {

  demux_matroska_t *this = (demux_matroska_t *) this_gen;
  int next_level;

  if (!parse_top_level(this, &next_level)) {
    this->status = DEMUX_FINISHED;
  }
  return this->status;
}


static int demux_matroska_get_status (demux_plugin_t *this_gen) {
  demux_matroska_t *this = (demux_matroska_t *) this_gen;

  return this->status;
}


static void demux_matroska_send_headers (demux_plugin_t *this_gen) {

  demux_matroska_t *this = (demux_matroska_t *) this_gen;
  int next_level;

  _x_demux_control_start (this->stream);

  if (!parse_segment(this))
    this->status = DEMUX_FINISHED;
  else
    this->status = DEMUX_OK;

  _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, (this->num_video_tracks != 0));
  _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, (this->num_audio_tracks != 0));

    
  /*
   * send preview buffers
   */
/*
  for (i = 0; i < NUM_PREVIEW_BUFFERS; i++) {
    if (!demux_mpgaudio_next (this, BUF_FLAG_PREVIEW)) {
      break;
    }
  }
    */

  /* enter in the segment */
  ebml_read_master (this->ebml, &this->segment);

  /* seek to the beginning of the segment */
  this->input->seek(this->input, this->segment.start, SEEK_SET);

  this->preview_sent = 0;
  this->preview_mode = 1;

  next_level = 1;
  while ((this->preview_sent < NUM_PREVIEW_BUFFERS) && (next_level == 1)) {
    if (!parse_top_level (this, &next_level)) {
      break;
    }
  }
  this->preview_mode = 0;

  /* seek to the beginning of the segment */
  this->input->seek(this->input, this->segment.start, SEEK_SET);
}


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

  demux_matroska_t *this = (demux_matroska_t *) this_gen;

  this->status = DEMUX_OK;

  return this->status;
}


static void demux_matroska_dispose (demux_plugin_t *this) {

  free (this);
}


static int demux_matroska_get_stream_length (demux_plugin_t *this_gen) {

  demux_matroska_t *this = (demux_matroska_t *) this_gen;

  return (int)this->duration;
}


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


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

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

  demux_matroska_t *this;
  ebml_parser_t    *ebml = NULL;

  lprintf("trying to open %s...\n", input->get_mrl(input));

  switch (stream->content_detection_method) {

  case METHOD_BY_CONTENT: {
    if (!(input->get_capabilities (input) & INPUT_CAP_SEEKABLE))
      return NULL;
    input->seek(input, 0, SEEK_SET);
    ebml = new_ebml_parser(stream->xine, input);
    if (!ebml_check_header(ebml))
      return NULL;
  }
  break;

  case METHOD_BY_EXTENSION: {
    char *mrl = input->get_mrl(input);
    char *extensions;

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

    extensions = class_gen->get_extensions (class_gen);

    if (!_x_demux_check_extension (mrl, extensions))
      return NULL;

  }
  break;

  case METHOD_EXPLICIT:
  break;

  default:
    return NULL;
  }

  this = xine_xmalloc (sizeof (demux_matroska_t));

  this->demux_plugin.send_headers      = demux_matroska_send_headers;
  this->demux_plugin.send_chunk        = demux_matroska_send_chunk;
  this->demux_plugin.seek              = demux_matroska_seek;
  this->demux_plugin.dispose           = demux_matroska_dispose;
  this->demux_plugin.get_status        = demux_matroska_get_status;
  this->demux_plugin.get_stream_length = demux_matroska_get_stream_length;
  this->demux_plugin.get_capabilities  = demux_matroska_get_capabilities;
  this->demux_plugin.get_optional_data = demux_matroska_get_optional_data;
  this->demux_plugin.demux_class       = class_gen;

  this->input      = input;
  this->status     = DEMUX_FINISHED;
  this->stream     = stream;

  if (!ebml) {
    ebml = new_ebml_parser(stream->xine, input);
    if (!ebml_check_header(ebml))
      goto error;
  }
  this->ebml = ebml;

  /* check header fields */
  if (ebml->max_id_len > 4)
    goto error;
  if (ebml->max_size_len > 8)
    goto error;
  if (strcmp(ebml->doctype, "matroska"))
    goto error;

  return &this->demux_plugin;

error:
  if (ebml)
    dispose_ebml_parser(ebml);
  free(this);
  return NULL;
}


/*
 * demux matroska class
 */

static char *get_description (demux_class_t *this_gen) {
  return "matroska demux plugin";
}


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


static char *get_extensions (demux_class_t *this_gen) {
  return "mkv";
}


static char *get_mimetypes (demux_class_t *this_gen) {
  return "video/mkv: mkv: matroska;";
}


static void class_dispose (demux_class_t *this_gen) {

  demux_matroska_class_t *this = (demux_matroska_class_t *) this_gen;

  free (this);
}

static void *init_class (xine_t *xine, void *data) {

  demux_matroska_class_t     *this;

  this         = xine_xmalloc (sizeof (demux_matroska_class_t));
  this->xine   = xine;

  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
 */

plugin_info_t xine_plugin_info[] = {
  /* type, API, "name", version, special_info, init_function */
  { PLUGIN_DEMUX, 23, "matroska", XINE_VERSION_CODE, NULL, init_class },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};