/*
 * This file is part of MPlayer.
 *
 * MPlayer 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.
 *
 * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <assert.h>
#include <math.h>
#include <inttypes.h>

#include "mp_msg.h"
#include "help_mp.h"
#include "mpcommon.h"
#include "stream/stream.h"
#include "demuxer.h"
#include "stheader.h"
#include "libavutil/intreadwrite.h"
#include "aviprint.h"
#include "demux_mov.h"
#include "demux_ogg.h"

#define FOURCC_OPUS   mmioFOURCC('o', 'p', 'u', 's')
#define FOURCC_VORBIS mmioFOURCC('v', 'r', 'b', 's')
#define FOURCC_SPEEX  mmioFOURCC('s', 'p', 'x', ' ')
#define FOURCC_THEORA mmioFOURCC('t', 'h', 'e', 'o')

#include <ogg/ogg.h>
#ifdef CONFIG_TREMOR
#include <tremor/ivorbiscodec.h>
#else
#include <vorbis/codec.h>
#endif

#ifdef CONFIG_OGGTHEORA
#include <theora/theoradec.h>
#endif

#define BLOCK_SIZE 4096

/* Theora decoder context : we won't be able to interpret granule positions
 * without using th_granule_time with the th_dec_ctx of the stream.
 * This is duplicated in `vd_theora.c'; put this in a common header?
 */
#ifdef CONFIG_OGGTHEORA
typedef struct theora_struct_st {
    th_setup_info *tsi;
    th_dec_ctx    *tctx;
    th_comment     tc;
    th_info        ti;
} theora_struct_t;
#endif

//// OggDS headers
// Header for the new header format
typedef struct stream_header_video {
    ogg_int32_t width;
    ogg_int32_t height;
} stream_header_video;

typedef struct stream_header_audio {
    ogg_int16_t channels;
    ogg_int16_t blockalign;
    ogg_int32_t avgbytespersec;
} stream_header_audio;

typedef struct __attribute__((__packed__)) stream_header {
    char streamtype[8];
    char subtype[4];

    ogg_int32_t size;               // size of the structure

    ogg_int64_t time_unit;          // in reference time
    ogg_int64_t samples_per_unit;
    ogg_int32_t default_len;        // in media time

    ogg_int32_t buffersize;
    ogg_int16_t bits_per_sample;

    ogg_int16_t padding;

    union {
        // Video specific
        stream_header_video	video;
        // Audio specific
        stream_header_audio	audio;
    } sh;
} stream_header;

/// Our private datas

typedef struct ogg_syncpoint {
    int64_t granulepos;
    off_t   page_pos;
} ogg_syncpoint_t;

/// A logical stream
typedef struct ogg_stream {
    /// Timestamping stuff
    float   samplerate; /// granulpos 2 time
    int64_t lastpos;
    int32_t lastsize;
    int     keyframe_granule_shift;

    // Logical stream state
    ogg_stream_state stream;
    int hdr_packets;
    int opus;
    int vorbis;
    int speex;
    int theora;
    int flac;
    int text;
    int id;

    vorbis_info vi;
    int         vi_initialized;

    void *ogg_d;
} ogg_stream_t;

typedef struct ogg_demuxer {
    /// Physical stream state
    ogg_sync_state   sync;
    /// Current page
    ogg_page         page;
    /// Logical streams
    ogg_stream_t    *subs;
    int              num_sub;
    ogg_syncpoint_t *syncpoints;
    int              num_syncpoint;
    off_t            pos, last_size;
    int64_t          initial_granulepos;
    int64_t          final_granulepos;
    int64_t          duration;

    /* Used for subtitle switching. */
    int    n_text;
    int   *text_ids;
    char **text_langs;
} ogg_demuxer_t;

#define NUM_VORBIS_HDR_PACKETS 3

/// Some defines from OggDS
#define PACKET_TYPE_HEADER  0x01
#define PACKET_TYPE_BITS    0x07
#define PACKET_LEN_BITS01   0xc0
#define PACKET_LEN_BITS2    0x02
#define PACKET_IS_SYNCPOINT 0x08

//-------- subtitle support - should be moved to decoder layer, and queue
//                          - subtitles up in demuxer buffer...

#include "sub/subreader.h"
#include "sub/sub.h"
#define OGG_SUB_MAX_LINE 128

static subtitle ogg_sub;
//FILE* subout;

static void demux_ogg_add_sub(ogg_stream_t *os, ogg_packet *pack)
{
    int lcv;
    char *packet = pack->packet;

    if (pack->bytes < 4)
        return;
    mp_msg(MSGT_DEMUX, MSGL_DBG2, "\ndemux_ogg_add_sub %02X %02X %02X '%s'\n",
           (unsigned char)packet[0],
           (unsigned char)packet[1],
           (unsigned char)packet[2],
           &packet[3]);

    if (((unsigned char)packet[0]) == 0x88) { // some subtitle text
        // Find data start
        double endpts = MP_NOPTS_VALUE;
        int32_t duration = 0;
        int16_t hdrlen = (*packet & PACKET_LEN_BITS01) >> 6, i;

        hdrlen |= (*packet & PACKET_LEN_BITS2) << 1;
        lcv = 1 + hdrlen;
        if (pack->bytes < lcv)
            return;
        for (i = hdrlen; i > 0; i--) {
            duration <<= 8;
            duration  |= (unsigned char)packet[i];
        }
        if (hdrlen > 0 && duration > 0) {
            double pts;

            if (pack->granulepos == -1)
                pack->granulepos = os->lastpos + os->lastsize;
            pts    = (double)pack->granulepos / (double)os->samplerate;
            endpts = 1.0 + pts + (double)duration / 1000.0;
        }
        sub_clear_text(&ogg_sub, MP_NOPTS_VALUE);
        sub_add_text(&ogg_sub, &packet[lcv], pack->bytes - lcv, endpts, 1);
    }

    mp_msg(MSGT_DEMUX, MSGL_DBG2, "Ogg sub lines: %d  first: '%s'\n",
            ogg_sub.lines, ogg_sub.text[0]);
#ifdef CONFIG_ICONV
    subcp_recode(&ogg_sub);
#endif
    vo_sub = &ogg_sub;
    vo_osd_changed(OSDTYPE_SUBTITLE);
}


// get the logical stream of the current page
// fill os if non NULL and return the stream id
static int demux_ogg_get_page_stream(ogg_demuxer_t *ogg_d,
                                     ogg_stream_state **os)
{
    int id, s_no;
    ogg_page *page = &ogg_d->page;

    s_no = ogg_page_serialno(page);

    for (id = 0; id < ogg_d->num_sub; id++)
        if (s_no == ogg_d->subs[id].stream.serialno)
            break;

    if (id == ogg_d->num_sub) {
        // If we have only one vorbis stream allow the stream id to change
        // it's normal on radio stream (each song have an different id).
        // But we (or the codec?) should check that the samplerate, etc
        // doesn't change (for radio stream it's ok)
        if (ogg_d->num_sub == 1 && ogg_d->subs[0].vorbis) {
            ogg_stream_reset(&ogg_d->subs[0].stream);
            ogg_stream_init(&ogg_d->subs[0].stream, s_no);
            id = 0;
        } else
            return -1;
    }

    if (os)
        *os = &ogg_d->subs[id].stream;

    return id;
}

static unsigned char *demux_ogg_read_packet(ogg_stream_t *os, ogg_packet *pack,
                                            double *pts, int *flags,
                                            int samplesize)
{
    unsigned char *data = pack->packet;

    *pts = MP_NOPTS_VALUE;
    *flags = 0;

    if (os->vorbis) {
        if (*pack->packet & PACKET_TYPE_HEADER) {
            os->hdr_packets++;
        } else {
            vorbis_info *vi;
            int32_t blocksize = 0;

            // When we dump the audio, there is no vi, but we don't care of timestamp in this case
            vi = os->vi_initialized ? &os->vi : NULL;
            if (vi)
                blocksize = vorbis_packet_blocksize(vi, pack) / samplesize;
            // Calculate the timestamp if the packet don't have any
            if (pack->granulepos == -1) {
                pack->granulepos = os->lastpos;
                if (os->lastsize > 0)
                    pack->granulepos += os->lastsize;
            } else
                *flags = 1;
            if (vi)
                *pts = pack->granulepos / (double)vi->rate;
            os->lastsize = blocksize;
            os->lastpos  = pack->granulepos;
        }
    } else if (os->speex || os->opus) {
        // whole packet (default)
# ifdef CONFIG_OGGTHEORA
    } else if (os->theora) {
        /* we pass complete packets to theora, mustn't strip the header! */
        os->lastsize = 1;

        /* header packets begin on 1-bit: thus check (*data&0x80).  We don't
           have theora_state st, until all header packets were passed to the
           decoder. */
        if (!pack->bytes || !(*data&0x80)) {
            int64_t iframemask = (1ull << os->keyframe_granule_shift) - 1;

            if (pack->granulepos >= 0) {
                os->lastpos  = pack->granulepos >> os->keyframe_granule_shift;
                os->lastpos += pack->granulepos & iframemask;
                *flags = (pack->granulepos & iframemask) == 0;
            } else {
                os->lastpos++;
            }
            pack->granulepos = os->lastpos;
            *pts = (double)os->lastpos / (double)os->samplerate;
        }
#endif /* CONFIG_OGGTHEORA */
    } else if (os->flac) {
        /* we pass complete packets to flac, mustn't strip the header! */
        if (os->flac == 2 && pack->packet[0] != 0xff)
            return NULL;
    } else {
        if (*pack->packet & PACKET_TYPE_HEADER) {
            os->hdr_packets++;
        } else {
            // Find data start
            int16_t hdrlen = (*pack->packet & PACKET_LEN_BITS01) >> 6;

            hdrlen |= (*pack->packet & PACKET_LEN_BITS2) << 1;
            data = pack->packet + 1 + hdrlen;
            // Calculate the timestamp
            if (pack->granulepos == -1)
                pack->granulepos = os->lastpos + (os->lastsize ? os->lastsize : 1);
            // If we already have a timestamp it can be a syncpoint
            if (*pack->packet & PACKET_IS_SYNCPOINT)
                *flags = 1;
            *pts = pack->granulepos / os->samplerate;
            // Save the packet length and timestamp
            os->lastsize = 0;
            while (hdrlen) {
                os->lastsize <<= 8;
                os->lastsize  |= pack->packet[hdrlen];
                hdrlen--;
            }
            os->lastpos = pack->granulepos;
        }
    }
    return data;
}

// check if clang has substring from comma separated langlist
static int demux_ogg_check_lang(const char *clang, const char *langlist)
{
    const char *c;

    if (!langlist || !*langlist)
        return 0;
    while ((c = strchr(langlist, ','))) {
        if (!strncasecmp(clang, langlist, c - langlist))
            return 1;
        langlist = &c[1];
    }
    if (!strncasecmp(clang, langlist, strlen(langlist)))
        return 1;
    return 0;
}

/** \brief Change the current subtitle stream and return its ID.

  \param demuxer The demuxer whose subtitle stream will be changed.
  \param new_num The number of the new subtitle track. The number must be
  between 0 and ogg_d->n_text - 1.

  \returns The Ogg stream number ( = page serial number) of the newly selected
  track.
  */
static int demux_ogg_sub_id(demuxer_t *demuxer, int index)
{
    ogg_demuxer_t *ogg_d = demuxer->priv;
    return (index < 0) ? index : (index >= ogg_d->n_text) ? -1 : ogg_d->text_ids[index];
}

/** \brief Translate the ogg track number into the subtitle number.
 *  \param demuxer The demuxer about whose subtitles we are inquiring.
 *  \param id The ogg track number of the subtitle track.
 */
static int demux_ogg_sub_reverse_id(demuxer_t *demuxer, int id)
{
    ogg_demuxer_t *ogg_d = demuxer->priv;
    int i;

    for (i = 0; i < ogg_d->n_text; i++)
        if (ogg_d->text_ids[i] == id)
            return i;
    return -1;
}

/// Try to print out comments and also check for LANGUAGE= tag
static void demux_ogg_check_comments(demuxer_t *d, ogg_stream_t *os,
                                     int id, vorbis_comment *vc)
{
    const char *hdr, *val;
    char **cmt = vc->user_comments;
    int index, i;
    ogg_demuxer_t *ogg_d = d->priv;
    static const struct table {
        const char *ogg;
        const char *mp;
    } table[] = {
        { "ENCODED_USING", "Software"      },
        { "ENCODER_URL",   "Encoder URL"   },
        { "TITLE",         "Title"         },
        { "ARTIST",        "Artist"        },
        { "COMMENT",       "Comments"      },
        { "DATE",          "Creation Date" },
        { "GENRE",         "Genre"         },
        { "ALBUM",         "Album"         },
        { "TRACKNUMBER",   "Track"         },
        { NULL, NULL },
    };

    while (*cmt) {
        hdr = NULL;
        if (!strncasecmp(*cmt, "LANGUAGE=", 9)) {
            val = *cmt + 9;
            if (ogg_d->subs[id].text)
                mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_SID_%d_LANG=%s\n",
                       ogg_d->subs[id].id, val);
            else if (id != d->video->id)
                mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_AID_%d_LANG=%s\n",
                       ogg_d->subs[id].id, val);
            if (ogg_d->subs[id].text)
                mp_msg(MSGT_DEMUX, MSGL_INFO,
                       "[Ogg] Language for -sid %d is '-slang \"%s\"'\n",
                       ogg_d->subs[id].id, val);
            // copy this language name into the array
            index = demux_ogg_sub_reverse_id(d, id);
            if (index >= 0) {
                sh_sub_t *sh;

                // in case of malicious files with more than one lang per track:
                free(ogg_d->text_langs[index]);
                ogg_d->text_langs[index] = strdup(val);
                sh = d->s_streams[index];
                if (sh)
                    free(sh->lang);
                if (sh)
                    sh->lang = strdup(val);
            }
            // check for -slang if subs are uninitialized yet
            if (os->text && d->sub->id < 0 && demux_ogg_check_lang(val, dvdsub_lang)) {
                d->sub->id = index;
                dvdsub_id  = index;
                mp_msg(MSGT_DEMUX, MSGL_V,
                       "Ogg demuxer: Displaying subtitle stream id %d which matched -slang %s\n",
                       id, val);
            }
            else
                hdr = "Language";
        }
        else {
            for (i = 0; table[i].ogg; i++) {
                if (!strncasecmp(*cmt, table[i].ogg, strlen(table[i].ogg)) &&
                        (*cmt)[strlen(table[i].ogg)] == '=') {
                    hdr = table[i].mp;
                    val = *cmt + strlen(table[i].ogg) + 1;
                }
            }
        }
        if (hdr)
            demux_info_add(d, hdr, val);
        mp_dbg(MSGT_DEMUX, MSGL_DBG2, " %s: %s\n", hdr, val);
        cmt++;
    }
}

/// Calculate the timestamp and add the packet to the demux stream
// return 1 if the packet was added, 0 otherwise
static int demux_ogg_add_packet(demux_stream_t *ds, ogg_stream_t *os,
                                int id, ogg_packet *pack)
{
    demuxer_t *d = ds->demuxer;
    demux_packet_t *dp;
    unsigned char *data;
    double pts = 0;
    int flags = 0;
    int samplesize = 1;

    // If packet is an comment header then we try to get comments at first
    if (pack->bytes >= 7 && !memcmp(pack->packet, "\003vorbis", 7)) {
        vorbis_info vi;
        vorbis_comment vc;

        vorbis_info_init(&vi);
        vorbis_comment_init(&vc);
        vi.rate = 1L; // it's checked by vorbis_synthesis_headerin()
        if (vorbis_synthesis_headerin(&vi, &vc, pack) == 0) // if no errors
            demux_ogg_check_comments(d, os, id, &vc);
        vorbis_comment_clear(&vc);
        vorbis_info_clear(&vi);
    }
    if (os->text) {
        if (id == demux_ogg_sub_id(d, d->sub->id)) // don't want to add subtitles to the demuxer for now
            demux_ogg_add_sub(os, pack);
        return 0;
    }
    if (os->speex) {
        // discard first two packets, they contain the header and comment
        if (os->hdr_packets < 2) {
            os->hdr_packets++;
            return 0;
        }
    } else {
        // If packet is an header we jump it except for vorbis and theora
        // (PACKET_TYPE_HEADER bit doesn't even exist for theora ?!)
        // We jump nothing for FLAC. Ain't this great? Packet contents have to be
        // handled differently for each and every stream type. The joy! The joy!
        if (!os->opus && !os->flac && (*pack->packet & PACKET_TYPE_HEADER) &&
                ds->sh &&
                (ds != d->audio || ((sh_audio_t*)ds->sh)->format != FOURCC_VORBIS || os->hdr_packets >= NUM_VORBIS_HDR_PACKETS ) &&
                (ds != d->video || (((sh_video_t*)ds->sh)->format != FOURCC_THEORA)))
            return 0;
    }

    // For vorbis packet the packet is the data, for other codec we must jump
    // the header
    if (ds == d->audio && ((sh_audio_t*)ds->sh)->format == FOURCC_VORBIS) {
        samplesize = ((sh_audio_t *)ds->sh)->samplesize;
    }
    data = demux_ogg_read_packet(os, pack, &pts, &flags, samplesize);
    if (!data)
        return 0;

    /// Clear subtitles if necessary (for broken files)
    if (sub_clear_text(&ogg_sub, pts)) {
        vo_sub = &ogg_sub;
        vo_osd_changed(OSDTYPE_SUBTITLE);
    }
    /// Send the packet
    dp = new_demux_packet(pack->bytes - (data - pack->packet));
    memcpy(dp->buffer, data, pack->bytes - (data - pack->packet));
    dp->pts   = pts;
    dp->flags = flags;
    ds_add_packet(ds, dp);
    mp_msg(MSGT_DEMUX, MSGL_DBG2,
           "New dp: %p  ds=%p  pts=%5.3f  len=%d  flag=%d  \n",
           dp, ds, pts, dp->len, flags);
    return 1;
}

/// if -forceidx build a table of all syncpoints to make seeking easier
/// otherwise try to get at least the final_granulepos
static void demux_ogg_scan_stream(demuxer_t *demuxer)
{
    ogg_demuxer_t *ogg_d = demuxer->priv;
    stream_t      *s     = demuxer->stream;
    ogg_sync_state *sync = &ogg_d->sync;
    ogg_page       *page = &ogg_d->page;
    ogg_stream_state *oss;
    ogg_stream_t *os;
    ogg_packet op;
    int np, sid, p, samplesize = 1;
    off_t pos, last_pos;

    pos = last_pos = demuxer->movi_start;

    // Reset the stream
    stream_seek(s, demuxer->movi_start);
    ogg_sync_reset(sync);

    // Get the serial number of the stream we use
    if (demuxer->video->id >= 0) {
        sid = demuxer->video->id;
    } else if (demuxer->audio->id >= 0) {
        sid = demuxer->audio->id;
        if (((sh_audio_t*)demuxer->audio->sh)->format == FOURCC_VORBIS)
            samplesize = ((sh_audio_t*)demuxer->audio->sh)->samplesize;
    } else
        return;
    os  = &ogg_d->subs[sid];
    oss = &os->stream;

    while (1) {
        np = ogg_sync_pageseek(sync, page);
        if (np < 0) { // We had to skip some bytes
            if (index_mode == 2)
                mp_msg(MSGT_DEMUX, MSGL_ERR,
                       "Bad page sync while building syncpoints table (%d)\n",
                       -np);
            pos += -np;
            continue;
        }
        if (np <= 0) { // We need more data
            char *buf = ogg_sync_buffer(sync, BLOCK_SIZE);
            int len   = stream_read(s, buf, BLOCK_SIZE);

            if (len == 0 && s->eof)
                break;
            ogg_sync_wrote(sync, len);
            continue;
        }
        // The page is ready
        //ogg_sync_pageout(sync, page);
        if (ogg_page_serialno(page) != os->stream.serialno) { // It isn't a page from the stream we want
            pos += np;
            continue;
        }
        if (ogg_stream_pagein(oss, page) != 0) {
            mp_msg(MSGT_DEMUX, MSGL_ERR, "Pagein error ????\n");
            pos += np;
            continue;
        }
        p = 0;
        while (ogg_stream_packetout(oss, &op) == 1) {
            double pts;
            int flags;

            demux_ogg_read_packet(os, &op, &pts, &flags, samplesize);
            if (op.granulepos >= 0) {
                ogg_d->final_granulepos = op.granulepos;
                if (ogg_d->initial_granulepos == MP_NOPTS_VALUE && (flags & 1)) {
                    ogg_d->initial_granulepos = op.granulepos;
                    if (index_mode != 2 && ogg_d->pos < demuxer->movi_end - 2 * 270000) {
                        //the 270000 are just a wild guess
                        stream_seek(s, FFMAX(ogg_d->pos, demuxer->movi_end - 270000));
                        ogg_sync_reset(sync);
                        continue;
                    }
                }
            }
            if (index_mode == 2 && (flags || (os->vorbis && op.granulepos >= 0))) {
                if (ogg_d->num_syncpoint > SIZE_MAX / sizeof(ogg_syncpoint_t) - 1)
                    break;
                ogg_d->syncpoints = realloc_struct(ogg_d->syncpoints, (ogg_d->num_syncpoint + 1), sizeof(ogg_syncpoint_t));
                ogg_d->syncpoints[ogg_d->num_syncpoint].granulepos = op.granulepos;
                ogg_d->syncpoints[ogg_d->num_syncpoint].page_pos   = (ogg_page_continued(page) && p == 0) ? last_pos : pos;
                ogg_d->num_syncpoint++;
            }
            p++;
        }
        if (p > 1 || (p == 1 && !ogg_page_continued(page)))
            last_pos = pos;
        pos += np;
        if (index_mode == 2)
            mp_msg(MSGT_DEMUX, MSGL_INFO, "Building syncpoint table %d%%\r",
                   (int)(pos * 100 / s->end_pos));
    }

    if (index_mode == 2) {
        mp_msg(MSGT_DEMUX, MSGL_INFO, "\n");
        mp_msg(MSGT_DEMUX, MSGL_V,
               "Ogg syncpoints table builed: %d syncpoints\n",
               ogg_d->num_syncpoint);
    }

    mp_msg(MSGT_DEMUX, MSGL_V, "Ogg stream length (granulepos): %"PRId64"\n",
           ogg_d->final_granulepos);

    stream_reset(s);
    stream_seek(s, demuxer->movi_start);
    ogg_sync_reset(sync);
    for (np = 0; np < ogg_d->num_sub; np++) {
        ogg_stream_reset(&ogg_d->subs[np].stream);
        ogg_d->subs[np].lastpos = ogg_d->subs[np].lastsize = ogg_d->subs[np].hdr_packets = 0;
    }

    // Get the first page
    while (1) {
        np = ogg_sync_pageout(sync, page);
        if (np <= 0) { // We need more data
            char *buf = ogg_sync_buffer(sync, BLOCK_SIZE);
            int len = stream_read(s, buf, BLOCK_SIZE);

            if (len == 0 && s->eof) {
                mp_msg(MSGT_DEMUX, MSGL_ERR, "EOF while trying to get the first page !!!!\n");
                break;
            }
            ogg_sync_wrote(sync, len);
            continue;
        }
        demux_ogg_get_page_stream(ogg_d, &oss);
        ogg_stream_pagein(oss, page);
        break;
    }
}

static void fixup_vorbis_wf(sh_audio_t *sh, ogg_demuxer_t *od)
{
    int i, offset;
    int ris, init_error = 0;
    ogg_packet op[3];
    unsigned char *buf[3];
    unsigned char *ptr;
    unsigned int len;
    ogg_stream_t *os = &od->subs[sh->ds->id];
    vorbis_comment vc;

    vorbis_info_init(&os->vi);
    vorbis_comment_init(&vc);
    for (i = 0; i < 3; i++) {
        op[i].bytes = ds_get_packet(sh->ds, &(op[i].packet));
        mp_msg(MSGT_DEMUX, MSGL_V, "fixup_vorbis_wf: i=%d, size=%ld\n", i, op[i].bytes);
        if (op[i].bytes < 0) {
            mp_msg(MSGT_DEMUX, MSGL_ERR, "Ogg demuxer error!, fixup_vorbis_wf: bad packet n. %d\n", i);
            return;
        }
        buf[i] = malloc(op[i].bytes);
        if (!buf[i])
            return;
        memcpy(buf[i], op[i].packet, op[i].bytes);

        op[i].b_o_s = (i == 0);
        ris = vorbis_synthesis_headerin(&os->vi, &vc, &op[i]);
        if (ris < 0) {
            init_error = 1;
            mp_msg(MSGT_DECAUDIO, MSGL_ERR, "DEMUX_OGG: header n. %d broken! len=%ld, code: %d\n", i, op[i].bytes, ris);
        }
    }
    vorbis_comment_clear(&vc);
    if (!init_error)
        os->vi_initialized = 1;

    len = op[0].bytes + op[1].bytes + op[2].bytes;
    sh->wf = calloc(1, sizeof(*sh->wf) + len + len / 255 + 64);
    ptr = (unsigned char*)(sh->wf + 1);

    ptr[0] = 2;
    offset = 1;
    offset += store_ughvlc(&ptr[offset], op[0].bytes);
    mp_msg(MSGT_DEMUX, MSGL_V, "demux_ogg, offset after 1st len = %u\n", offset);
    offset += store_ughvlc(&ptr[offset], op[1].bytes);
    mp_msg(MSGT_DEMUX, MSGL_V, "demux_ogg, offset after 2nd len = %u\n", offset);
    for (i = 0; i < 3; i++) {
        mp_msg(MSGT_DEMUX, MSGL_V, "demux_ogg, i=%d, bytes: %ld, offset: %u\n", i, op[i].bytes, offset);
        memcpy(&ptr[offset], buf[i], op[i].bytes);
        offset += op[i].bytes;
    }
    sh->wf->cbSize = offset;
    mp_msg(MSGT_DEMUX, MSGL_V, "demux_ogg, extradata size: %d\n", sh->wf->cbSize);
    sh->wf = realloc(sh->wf, sizeof(*sh->wf) + sh->wf->cbSize);

    if (op[0].bytes >= 29) {
        unsigned int br;
        int nombr, minbr, maxbr;

        ptr = buf[0];
        sh->channels   = ptr[11];
        sh->samplerate = sh->wf->nSamplesPerSec = AV_RL32(&ptr[12]);
        maxbr = AV_RL32(&ptr[16]);  //max
        nombr = AV_RL32(&ptr[20]);  //nominal
        minbr = AV_RL32(&ptr[24]);  //minimum

        if (maxbr == -1)
            maxbr = 0;
        if (nombr == -1)
            nombr = 0;
        if (minbr == -1)
            minbr = 0;

        br = maxbr / 8;
        if (!br)
            br = nombr / 8;
        if (!br)
            br = minbr / 8;
        sh->wf->nAvgBytesPerSec = br;
        sh->wf->wBitsPerSample  = 16;
        sh->samplesize = (sh->wf->wBitsPerSample + 7) / 8;

        mp_msg(MSGT_DEMUX, MSGL_V,
               "demux_ogg, vorbis stream features are: channels: %d, srate: %d, bitrate: %d, max: %u, nominal: %u, min: %u\n",
               sh->channels, sh->samplerate, sh->wf->nAvgBytesPerSec,
               maxbr, nombr, minbr);
    }
    free(buf[2]);
    free(buf[1]);
    free(buf[0]);
}

/// Open an ogg physical stream
// Not static because it's used also in demuxer_avi.c
int demux_ogg_open(demuxer_t *demuxer)
{
    ogg_demuxer_t *ogg_d;
    stream_t *s;
    char *buf;
    int np, s_no, n_audio = 0, n_video = 0;
    ogg_sync_state *sync;
    ogg_page *page;
    ogg_packet pack;
    sh_audio_t *sh_a;
    sh_video_t *sh_v;

#ifdef CONFIG_ICONV
    subcp_open(NULL);
#endif

    s = demuxer->stream;

    demuxer->priv = ogg_d = calloc(1, sizeof(*ogg_d));
    sync = &ogg_d->sync;
    page = &ogg_d->page;

    ogg_sync_init(sync);

    while (1) {
        /// Try to get a page
        ogg_d->pos += ogg_d->last_size;
        np = ogg_sync_pageseek(sync, page);
        /// Error
        if (np < 0) {
            mp_msg(MSGT_DEMUX, MSGL_DBG2, "Ogg demuxer : Bad page sync\n");
            goto err_out;
        }
        /// Need some more data
        if (np == 0) {
            int len;

            buf = ogg_sync_buffer(sync, BLOCK_SIZE);
            len = stream_read(s, buf, BLOCK_SIZE);
            if (len == 0 && s->eof) {
                goto err_out;
            }
            ogg_sync_wrote(sync, len);
            continue;
        }
        ogg_d->last_size = np;
        // We got one page now

        if (!ogg_page_bos(page)) { // It's not a beginning page
            // Header parsing end here, we need to get the page otherwise it will be lost
            int id = demux_ogg_get_page_stream(ogg_d, NULL);
            if (id >= 0)
                ogg_stream_pagein(&ogg_d->subs[id].stream, page);
            else
                mp_msg(MSGT_DEMUX, MSGL_ERR,
                       "Ogg : Warning found none bos page from unknown stream %d\n",
                       ogg_page_serialno(page));
            break;
        }

        /// Init  the data structure needed for a logical stream
        ogg_d->subs = realloc_struct(ogg_d->subs, ogg_d->num_sub+1,
                                     sizeof(ogg_stream_t));
        memset(&ogg_d->subs[ogg_d->num_sub], 0, sizeof(ogg_stream_t));
        /// Get the stream serial number
        s_no = ogg_page_serialno(page);
        ogg_stream_init(&ogg_d->subs[ogg_d->num_sub].stream, s_no);
        mp_msg(MSGT_DEMUX, MSGL_DBG2,
               "Ogg : Found a stream with serial=%d\n", s_no);
        // Take the first page
        ogg_stream_pagein(&ogg_d->subs[ogg_d->num_sub].stream, page);
        // Get first packet of the page
        ogg_stream_packetout(&ogg_d->subs[ogg_d->num_sub].stream, &pack);

        // Reset our vars
        sh_a = NULL;
        sh_v = NULL;

        ogg_d->subs[ogg_d->num_sub].ogg_d = ogg_d;

        // Check for Vorbis
        if (pack.bytes >= 7 && !strncmp(&pack.packet[1], "vorbis", 6)) {
            sh_a = new_sh_audio_aid(demuxer, ogg_d->num_sub, n_audio, NULL);
            sh_a->format = FOURCC_VORBIS;
            ogg_d->subs[ogg_d->num_sub].vorbis = 1;
            ogg_d->subs[ogg_d->num_sub].id     = n_audio;
            n_audio++;
            mp_msg(MSGT_DEMUX, MSGL_INFO,
                   "[Ogg] stream %d: audio (Vorbis), -aid %d\n",
                   ogg_d->num_sub, n_audio - 1);
        } else if (pack.bytes >= 8 && !strncmp(pack.packet, "OpusHead", 8)) {
            sh_a = new_sh_audio_aid(demuxer, ogg_d->num_sub, n_audio, NULL);
            sh_a->format = FOURCC_OPUS;
            ogg_d->subs[ogg_d->num_sub].opus = 1;
            ogg_d->subs[ogg_d->num_sub].id   = n_audio;
            n_audio++;
            mp_msg(MSGT_DEMUX, MSGL_INFO,
                   "[Ogg] stream %d: audio (Opus), -aid %d\n",
                   ogg_d->num_sub, n_audio - 1);
        } else if (pack.bytes >= 80 && !strncmp(pack.packet, "Speex", 5)) {
            sh_a = new_sh_audio_aid(demuxer, ogg_d->num_sub, n_audio, NULL);
            sh_a->wf         = calloc(1, sizeof(*sh_a->wf) + pack.bytes);
            sh_a->format     = FOURCC_SPEEX;
            sh_a->samplerate = sh_a->wf->nSamplesPerSec = AV_RL32(&pack.packet[36]);
            sh_a->channels   = sh_a->wf->nChannels = AV_RL32(&pack.packet[48]);
            sh_a->wf->wFormatTag      = sh_a->format;
            sh_a->wf->nAvgBytesPerSec = AV_RL32(&pack.packet[52]);
            sh_a->wf->nBlockAlign     = 0;
            sh_a->wf->wBitsPerSample  = 16;
            sh_a->samplesize = 2;
            sh_a->wf->cbSize = pack.bytes;
            memcpy(&sh_a->wf[1], pack.packet, pack.bytes);

            ogg_d->subs[ogg_d->num_sub].samplerate = sh_a->samplerate;
            ogg_d->subs[ogg_d->num_sub].speex      = 1;
            ogg_d->subs[ogg_d->num_sub].id         = n_audio;
            n_audio++;
            mp_msg(MSGT_DEMUX, MSGL_INFO,
                   "[Ogg] stream %d: audio (Speex), -aid %d\n",
                   ogg_d->num_sub, n_audio - 1);

            // check for Theora
#ifdef CONFIG_OGGTHEORA
        } else if (pack.bytes >= 7 && !strncmp (&pack.packet[1], "theora", 6)) {
            int errorCode = 0;
            th_info ti;
            th_comment tc;
            th_setup_info *tsi = NULL;

            th_info_init (&ti);
            th_comment_init (&tc);

            errorCode = th_decode_headerin(&ti, &tc, &tsi, &pack);
            if (errorCode < 0) {
                mp_msg(MSGT_DEMUX, MSGL_ERR,
                       "Theora header parsing failed: %i \n", errorCode);
            } else {
                sh_v = new_sh_video_vid(demuxer, ogg_d->num_sub, n_video);

                sh_v->bih = calloc(1, sizeof(*sh_v->bih));
                sh_v->bih->biSize        = sizeof(*sh_v->bih);
                sh_v->bih->biCompression = sh_v->format = FOURCC_THEORA;
                sh_v->fps = ((double)ti.fps_numerator) / (double)ti.fps_denominator;
                sh_v->frametime = ((double)ti.fps_denominator) / (double)ti.fps_numerator;
                sh_v->i_bps  = ti.target_bitrate / 8;
                sh_v->disp_w = sh_v->bih->biWidth  = ti.frame_width;
                sh_v->disp_h = sh_v->bih->biHeight = ti.frame_height;
                sh_v->bih->biBitCount  = 24;
                sh_v->bih->biPlanes    = 3;
                sh_v->bih->biSizeImage = ((sh_v->bih->biBitCount / 8) * sh_v->bih->biWidth * sh_v->bih->biHeight);
                ogg_d->subs[ogg_d->num_sub].samplerate               = sh_v->fps;
                ogg_d->subs[ogg_d->num_sub].theora                   = 1;
                ogg_d->subs[ogg_d->num_sub].keyframe_granule_shift   = ti.keyframe_granule_shift;
                ogg_d->subs[ogg_d->num_sub].id                       = n_video;
                n_video++;
                mp_msg(MSGT_DEMUX, MSGL_INFO,
                       "[Ogg] stream %d: video (Theora v%d.%d.%d), -vid %d\n",
                       ogg_d->num_sub,
                       (int)ti.version_major,
                       (int)ti.version_minor,
                       (int)ti.version_subminor,
                       n_video - 1);
                if (mp_msg_test(MSGT_HEADER, MSGL_V))
                    print_video_header(sh_v->bih, MSGL_V);
            }
            th_comment_clear(&tc);
            th_info_clear(&ti);
            th_setup_free(tsi);
#endif /* CONFIG_OGGTHEORA */
        } else if (pack.bytes >= 4 && !strncmp (&pack.packet[0], "fLaC", 4)) {
            sh_a = new_sh_audio_aid(demuxer, ogg_d->num_sub, n_audio, NULL);
            sh_a->format = mmioFOURCC('f', 'L', 'a', 'C');
            ogg_d->subs[ogg_d->num_sub].id = n_audio;
            n_audio++;
            ogg_d->subs[ogg_d->num_sub].flac = 1;
            sh_a->wf = NULL;
            mp_msg(MSGT_DEMUX, MSGL_INFO,
                   "[Ogg] stream %d: audio (FLAC), -aid %d\n",
                   ogg_d->num_sub, n_audio - 1);
        } else if (pack.bytes >= 51 && !strncmp(&pack.packet[1], "FLAC", 4)) {
            sh_a = new_sh_audio_aid(demuxer, ogg_d->num_sub, n_audio, NULL);
            sh_a->format = mmioFOURCC('f', 'L', 'a', 'C');
            ogg_d->subs[ogg_d->num_sub].id = n_audio;
            n_audio++;
            ogg_d->subs[ogg_d->num_sub].flac = 2;
            sh_a->wf = calloc(1, sizeof(*sh_a->wf) + 34);
            sh_a->wf->wFormatTag = sh_a->format;
            sh_a->wf->cbSize     = 34;
            memcpy(&sh_a->wf[1], &pack.packet[17], 34);
            mp_msg(MSGT_DEMUX, MSGL_INFO,
                   "[Ogg] stream %d: audio (FLAC, try 2), -aid %d\n",
                   ogg_d->num_sub, n_audio - 1);

            /// Check for old header
        } else if (pack.bytes >= 142 &&
                   !strncmp(&pack.packet[1], "Direct Show Samples embedded in Ogg", 35)) {

            // Old video header
            if (AV_RL32(pack.packet + 96) == 0x05589f80 && pack.bytes >= 184) {
                sh_v = new_sh_video_vid(demuxer, ogg_d->num_sub, n_video);
                sh_v->bih = calloc(1, sizeof(*sh_v->bih));
                sh_v->bih->biSize        = sizeof(*sh_v->bih);
                sh_v->bih->biCompression = sh_v->format = mmioFOURCC(pack.packet[68], pack.packet[69],
                                                                     pack.packet[70], pack.packet[71]);
                sh_v->frametime = AV_RL64(pack.packet + 164) * 0.0000001;
                sh_v->fps    = 1 / sh_v->frametime;
                sh_v->disp_w = sh_v->bih->biWidth = AV_RL32(pack.packet + 176);
                sh_v->disp_h = sh_v->bih->biHeight = AV_RL32(pack.packet + 180);
                sh_v->bih->biBitCount = AV_RL16(pack.packet + 182);
                if (!sh_v->bih->biBitCount)
                    sh_v->bih->biBitCount = 24; // hack, FIXME
                sh_v->bih->biPlanes    = 1;
                sh_v->bih->biSizeImage = (sh_v->bih->biBitCount >> 3) * sh_v->bih->biWidth * sh_v->bih->biHeight;

                ogg_d->subs[ogg_d->num_sub].samplerate = sh_v->fps;
                ogg_d->subs[ogg_d->num_sub].id         = n_video;
                n_video++;
                mp_msg(MSGT_DEMUX, MSGL_INFO,
                       "[Ogg] stream %d: video (FOURCC %c%c%c%c), -vid %d\n",
                       ogg_d->num_sub, pack.packet[68], pack.packet[69],
                       pack.packet[70], pack.packet[71], n_video - 1);
                if (mp_msg_test(MSGT_HEADER, MSGL_V))
                    print_video_header(sh_v->bih, MSGL_V);
                // Old audio header
            } else if (AV_RL32(pack.packet + 96) == 0x05589F81) {
                unsigned int extra_size;

                sh_a = new_sh_audio_aid(demuxer, ogg_d->num_sub, n_audio, NULL);
                extra_size = AV_RL16(pack.packet + 140);
                sh_a->wf         = calloc(1, sizeof(*sh_a->wf) + extra_size);
                sh_a->format     = sh_a->wf->wFormatTag     = AV_RL16(pack.packet + 124);
                sh_a->channels   = sh_a->wf->nChannels      = AV_RL16(pack.packet + 126);
                sh_a->samplerate = sh_a->wf->nSamplesPerSec = AV_RL32(pack.packet + 128);
                sh_a->wf->nAvgBytesPerSec = AV_RL32(pack.packet + 132);
                sh_a->wf->nBlockAlign     = AV_RL16(pack.packet + 136);
                sh_a->wf->wBitsPerSample  = AV_RL16(pack.packet + 138);
                sh_a->samplesize = (sh_a->wf->wBitsPerSample + 7) / 8;
                sh_a->wf->cbSize = extra_size;
                if (extra_size > 0)
                    memcpy(sh_a->wf + 1,
                           pack.packet + 142, extra_size);

                ogg_d->subs[ogg_d->num_sub].samplerate = sh_a->samplerate; // * sh_a->channels;
                ogg_d->subs[ogg_d->num_sub].id         = n_audio;
                n_audio++;
                mp_msg(MSGT_DEMUX, MSGL_INFO,
                       "[Ogg] stream %d: audio (format 0x%04x), -aid %d\n",
                       ogg_d->num_sub, sh_a->format, n_audio - 1);
                if (mp_msg_test(MSGT_HEADER, MSGL_V))
                    print_wave_header(sh_a->wf, MSGL_V);
            } else
                mp_msg(MSGT_DEMUX, MSGL_WARN,
                       "Ogg stream %d contains an old header but the header type is unknown\n",
                       ogg_d->num_sub);

            // Check new header
        } else if ((*pack.packet & PACKET_TYPE_BITS ) == PACKET_TYPE_HEADER &&
                   pack.bytes >= (int)sizeof(stream_header) + 1) {
            stream_header *st = (stream_header*)(pack.packet + 1);
            /// New video header
            if (strncmp(st->streamtype, "video", 5) == 0) {
                sh_v = new_sh_video_vid(demuxer, ogg_d->num_sub, n_video);
                sh_v->bih         = calloc(1, sizeof(*sh_v->bih));
                sh_v->bih->biSize = sizeof(*sh_v->bih);
                sh_v->bih->biCompression = sh_v->format = mmioFOURCC(st->subtype[0], st->subtype[1],
                                                                     st->subtype[2], st->subtype[3]);
                sh_v->frametime = AV_RL64(&st->time_unit) * 0.0000001;
                sh_v->fps       = 1.0 / sh_v->frametime;
                sh_v->bih->biBitCount = AV_RL16(&st->bits_per_sample);
                sh_v->disp_w = sh_v->bih->biWidth  = AV_RL32(&st->sh.video.width);
                sh_v->disp_h = sh_v->bih->biHeight = AV_RL32(&st->sh.video.height);
                if (!sh_v->bih->biBitCount)
                    sh_v->bih->biBitCount = 24; // hack, FIXME
                sh_v->bih->biPlanes    = 1;
                sh_v->bih->biSizeImage = (sh_v->bih->biBitCount >> 3) * sh_v->bih->biWidth * sh_v->bih->biHeight;

                ogg_d->subs[ogg_d->num_sub].samplerate = sh_v->fps;
                ogg_d->subs[ogg_d->num_sub].id         = n_video;
                n_video++;
                mp_msg(MSGT_DEMUX, MSGL_INFO,
                       "[Ogg] stream %d: video (FOURCC %c%c%c%c), -vid %d\n",
                       ogg_d->num_sub, st->subtype[0], st->subtype[1],
                       st->subtype[2], st->subtype[3], n_video - 1);
                if (mp_msg_test(MSGT_HEADER, MSGL_V))
                    print_video_header(sh_v->bih, MSGL_V);
                /// New audio header
            } else if (strncmp(st->streamtype, "audio", 5) == 0) {
                char buffer[5];
                unsigned int extra_size = AV_RL32(&st->size) - sizeof(stream_header);
                unsigned int extra_offset = 0;

                memcpy(buffer, st->subtype, 4);
                buffer[4] = '\0';

                /* Nasty workaround. stream_header.size seems not to contain the real
                   size in all cases. There are four extra bytes that are unaccounted
                   for in front of the real codec initialization data _at least_ for
                   AAC. So far I've only seen those bytes being all 0, so we can
                   just skip them here. */
                if ((strtol(buffer, NULL, 16) == 0xff) && (extra_size >= 4)) {
                    extra_size   -= 4;
                    extra_offset  = 4;
                }

                sh_a = new_sh_audio_aid(demuxer, ogg_d->num_sub, n_audio, NULL);
                sh_a->wf         = calloc(1, sizeof(*sh_a->wf) + extra_size);
                sh_a->format     = sh_a->wf->wFormatTag = strtol(buffer, NULL, 16);
                sh_a->channels   = sh_a->wf->nChannels  = AV_RL16(&st->sh.audio.channels);
                sh_a->samplerate = sh_a->wf->nSamplesPerSec = AV_RL64(&st->samples_per_unit);
                sh_a->wf->nAvgBytesPerSec = AV_RL32(&st->sh.audio.avgbytespersec);
                sh_a->wf->nBlockAlign     = AV_RL16(&st->sh.audio.blockalign);
                sh_a->wf->wBitsPerSample  = AV_RL16(&st->bits_per_sample);
                sh_a->samplesize = (sh_a->wf->wBitsPerSample + 7) / 8;
                sh_a->wf->cbSize = extra_size;
                if (extra_size)
                    memcpy(sh_a->wf+1,
                           ((char *)(st+1))+extra_offset, extra_size);

                ogg_d->subs[ogg_d->num_sub].samplerate = sh_a->samplerate; // * sh_a->channels;
                ogg_d->subs[ogg_d->num_sub].id = n_audio;
                n_audio++;
                mp_msg(MSGT_DEMUX, MSGL_INFO,
                       "[Ogg] stream %d: audio (format 0x%04x), -aid %d\n",
                       ogg_d->num_sub, sh_a->format, n_audio - 1);
                if (mp_msg_test(MSGT_HEADER, MSGL_V))
                    print_wave_header(sh_a->wf, MSGL_V);

                /// Check for text (subtitles) header
            } else if (strncmp(st->streamtype, "text", 4) == 0) {
                mp_msg(MSGT_DEMUX, MSGL_INFO,
                       "[Ogg] stream %d: subtitles (SRT-like text subtitles), -sid %d\n",
                       ogg_d->num_sub, ogg_d->n_text);
                ogg_d->subs[ogg_d->num_sub].samplerate = AV_RL64(&st->time_unit) / 10;
                ogg_d->subs[ogg_d->num_sub].text       = 1;
                ogg_d->subs[ogg_d->num_sub].id         = ogg_d->n_text;
                new_sh_sub(demuxer, ogg_d->n_text, NULL);
                ogg_d->n_text++;
                ogg_d->text_ids = realloc_struct(ogg_d->text_ids, ogg_d->n_text, sizeof(*ogg_d->text_ids));
                ogg_d->text_ids[ogg_d->n_text - 1] = ogg_d->num_sub;
                ogg_d->text_langs = realloc_struct(ogg_d->text_langs, ogg_d->n_text, sizeof(*ogg_d->text_langs));
                ogg_d->text_langs[ogg_d->n_text - 1] = NULL;
                //// Unknown header type
            } else
                mp_msg(MSGT_DEMUX, MSGL_ERR,
                       "Ogg stream %d has a header marker but is of an unknown type\n",
                       ogg_d->num_sub);
            /// Unknown (invalid ?) header
        } else
            mp_msg(MSGT_DEMUX, MSGL_ERR,
                   "Ogg stream %d is of an unknown type\n",
                   ogg_d->num_sub);

        if (sh_a || sh_v) {
            demux_stream_t *ds = NULL;
            if (sh_a) {
                // If the audio stream is not defined we took the first one
                if (demuxer->audio->id == -1) {
                    demuxer->audio->id = ogg_d->num_sub;
                    demuxer->audio->sh = sh_a;
                    //if (sh_a->wf) print_wave_header(sh_a->wf, MSGL_INFO);
                }
                /// Is it the stream we want
                if (demuxer->audio->sh == sh_a) {
                    ds = demuxer->audio;
                }
            }
            if (sh_v) {
                /// Also for video
                if (demuxer->video->id == -1) {
                    demuxer->video->id = ogg_d->num_sub;
                    demuxer->video->sh = sh_v;
                    //if (sh_v->bih) print_video_header(sh_v->bih, MSGL_INFO);
                }
                if (demuxer->video->sh == sh_v) {
                    ds = demuxer->video;
                }
            }
            /// Add the header packets if the stream isn't seekable
            if (ds && !s->end_pos) {
                /// Finish the page, otherwise packets will be lost
                do {
                    demux_ogg_add_packet(ds, &ogg_d->subs[ogg_d->num_sub],
                                         ogg_d->num_sub, &pack);
                } while (ogg_stream_packetout(&ogg_d->subs[ogg_d->num_sub].stream, &pack) == 1);
            }
        }
        ogg_d->num_sub++;
    }

    if (!n_video && !n_audio) {
        goto err_out;
    }

    if (!demuxer->video->sh)
        demuxer->video->id = -2;
    if (!demuxer->audio->sh)
        demuxer->audio->id = -2;
    if (!demuxer->sub->sh)
        demuxer->sub->id = -2;

    ogg_d->final_granulepos   = 0;
    ogg_d->initial_granulepos = MP_NOPTS_VALUE;
    if (!s->end_pos) {
        demuxer->seekable = 0;
    } else {
        demuxer->movi_start = s->start_pos; // Needed for XCD (Ogg written in MODE2)
        demuxer->movi_end   = s->end_pos;
        demuxer->seekable   = 1;
        demux_ogg_scan_stream(demuxer);
    }
    if (ogg_d->initial_granulepos == MP_NOPTS_VALUE)
        ogg_d->initial_granulepos = 0;
    ogg_d->duration = ogg_d->final_granulepos - ogg_d->initial_granulepos;

    mp_msg(MSGT_DEMUX, MSGL_V,
           "Ogg demuxer : found %d audio stream%s, %d video stream%s and %d text stream%s\n",
           n_audio, n_audio > 1 ? "s" : "",
           n_video, n_video > 1 ? "s" : "",
           ogg_d->n_text, ogg_d->n_text > 1 ? "s" : "");

    sh_a = demuxer->audio->sh;
    if (sh_a && sh_a->format == FOURCC_VORBIS)
        fixup_vorbis_wf(sh_a, ogg_d);

    return DEMUXER_TYPE_OGG;

err_out:
    return 0;
}

static int demux_ogg_fill_buffer(demuxer_t *d, demux_stream_t *dsds)
{
    ogg_demuxer_t *ogg_d;
    stream_t *s;
    demux_stream_t *ds;
    ogg_sync_state *sync;
    ogg_stream_state *os;
    ogg_page *page;
    ogg_packet pack;
    int np = 0, id=0;

    s = d->stream;
    ogg_d = d->priv;
    sync = &ogg_d->sync;
    page = &ogg_d->page;

    /// Find the stream we are working on
    if ((id = demux_ogg_get_page_stream(ogg_d, &os)) < 0) {
        mp_msg(MSGT_DEMUX, MSGL_ERR, "Ogg demuxer : can't get current stream\n");
        return 0;
    }

    while (1) {
        np = 0;
        ds = NULL;
        /// Try to get some packet from the current page
        while ((np = ogg_stream_packetout(os, &pack)) != 1) {
            /// No packet we go the next page
            if (np == 0) {
                while (1) {
                    int pa, len;
                    char *buf;

                    ogg_d->pos += ogg_d->last_size;
                    /// Get the next page from the physical stream
                    while ((pa = ogg_sync_pageseek(sync, page)) <= 0) {
                        /// Error : we skip some bytes
                        if (pa < 0) {
                            mp_msg(MSGT_DEMUX, MSGL_WARN,
                                   "Ogg : Page out not synced, we skip some bytes\n");
                            ogg_d->pos -= pa;
                            continue;
                        }
                        /// We need more data
                        buf = ogg_sync_buffer(sync, BLOCK_SIZE);
                        len = stream_read(s, buf, BLOCK_SIZE);
                        if (len == 0 && s->eof) {
                            mp_msg(MSGT_DEMUX, MSGL_DBG2, "Ogg : Stream EOF !!!!\n");
                            return 0;
                        }
                        ogg_sync_wrote(sync, len);
                    } /// Page loop
                    ogg_d->last_size = pa;
                    /// Find the page's logical stream
                    if ((id = demux_ogg_get_page_stream(ogg_d, &os)) < 0) {
                        mp_msg(MSGT_DEMUX, MSGL_ERR,
                               "Ogg demuxer error : we met an unknown stream\n");
                        return 0;
                    }
                    /// Take the page
                    if (ogg_stream_pagein(os, page) == 0)
                        break;
                    /// Page was invalid => retry
                    mp_msg(MSGT_DEMUX, MSGL_WARN,
                           "Ogg demuxer : got invalid page !!!!!\n");
                    ogg_d->pos += ogg_d->last_size;
                }
            } else /// Packet was corrupted
                mp_msg(MSGT_DEMUX, MSGL_WARN,
                       "Ogg : bad packet in stream %d\n", id);
        } /// Packet loop

        /// Is the actual logical stream in use ?
        if (id == d->audio->id)
            ds = d->audio;
        else if (id == d->video->id)
            ds = d->video;
        else if (ogg_d->subs[id].text)
            ds = d->sub;

        if (ds) {
            if (!demux_ogg_add_packet(ds, &ogg_d->subs[id], id, &pack))
                continue; /// Unuseful packet, get another
            d->filepos = ogg_d->pos;
            return 1;
        }
    } /// while (1)
}

/// For avi with Ogg audio stream we have to create an ogg demuxer for this
// stream, then we join the avi and ogg demuxer with a demuxers demuxer
demuxer_t *init_avi_with_ogg(demuxer_t *demuxer)
{
    demuxer_t *od;
    ogg_demuxer_t *ogg_d = NULL;
    stream_t *s;
    uint32_t hdrsizes[3];
    demux_packet_t *dp;
    sh_audio_t *sh_audio = demuxer->audio->sh;
    int np;
    uint8_t *extradata = (uint8_t *)(sh_audio->wf + 1);
    int i;
    unsigned char *p = NULL, *buf;
    int plen;

    /// Check that the cbSize is big enough for the following reads
    if (sh_audio->wf->cbSize < 22 + 3 * 4) {
        mp_msg(MSGT_DEMUX, MSGL_ERR,
               "AVI Ogg : Initial audio header is too small !!!!!\n");
        goto fallback;
    }
    /// Get the size of the 3 header packet
    extradata += 22;
    for (i = 0; i < 3; i++) {
        hdrsizes[i] = AV_RL32(extradata);
        extradata += 4;
    }
    //  printf("\n!!!!!! hdr sizes: %d %d %d   \n", hdrsizes[0], hdrsizes[1], hdrsizes[2]);

    /// Check the size
    if (sh_audio->wf->cbSize < 22 + 3 * 4 + hdrsizes[0] + hdrsizes[1] + hdrsizes[2]) {
        mp_msg(MSGT_DEMUX, MSGL_ERR,
               "AVI Ogg : Audio header is too small !!!!!\n");
        goto fallback;
    }

    // Build the ogg demuxer private datas
    ogg_d = calloc(1, sizeof(*ogg_d));
    ogg_d->num_sub = 1;
    ogg_d->subs    = malloc(sizeof(*ogg_d->subs));
    ogg_d->subs[0].vorbis = 1;

    // Init the ogg physical stream
    ogg_sync_init(&ogg_d->sync);

    // Get the first page of the stream : we assume there only 1 logical stream
    while ((np = ogg_sync_pageout(&ogg_d->sync, &ogg_d->page)) <= 0 ) {
        if (np < 0) {
            mp_msg(MSGT_DEMUX, MSGL_ERR,
                   "AVI Ogg error : Can't init using first stream packets\n");
            goto fallback;
        }
        // Add some data
        plen = ds_get_packet(demuxer->audio, &p);
        if (plen < 0) {
            mp_msg(MSGT_DEMUX, MSGL_ERR,
                   "AVI Ogg error : Invalid first stream packets\n");
            goto fallback;
        }
        buf  = ogg_sync_buffer(&ogg_d->sync, plen);
        memcpy(buf, p, plen);
        ogg_sync_wrote(&ogg_d->sync, plen);
    }
    // Init the logical stream
    mp_msg(MSGT_DEMUX, MSGL_DBG2,
           "AVI Ogg found page with serial %d\n",
           ogg_page_serialno(&ogg_d->page));
    ogg_stream_init(&ogg_d->subs[0].stream, ogg_page_serialno(&ogg_d->page));
    // Write the page
    ogg_stream_pagein(&ogg_d->subs[0].stream, &ogg_d->page);

    // Create the ds_stream and the ogg demuxer
    s = new_ds_stream(demuxer->audio);
    od = new_demuxer(s, DEMUXER_TYPE_OGG, 0, -2, -2, NULL);

    /// Add the header packets in the ogg demuxer audio stream
    for (i = 0; i < 3; i++) {
        dp = new_demux_packet(hdrsizes[i]);
        memcpy(dp->buffer, extradata, hdrsizes[i]);
        ds_add_packet(od->audio, dp);
        extradata += hdrsizes[i];
    }

    // Finish setting up the ogg demuxer
    od->priv = ogg_d;
    sh_audio = new_sh_audio(od, 0, NULL);
    od->audio->id = 0;
    od->video->id = -2;
    od->audio->sh = sh_audio;
    sh_audio->format = FOURCC_VORBIS;
    fixup_vorbis_wf(sh_audio, ogg_d);

    /// Return the joined demuxers
    return new_demuxers_demuxer(demuxer, od, demuxer);

fallback:
    free(ogg_d);
    demuxer->audio->id = -2;
    return demuxer;

}

static void demux_ogg_seek(demuxer_t *demuxer, float rel_seek_secs,
                           float audio_delay, int flags)
{
    ogg_demuxer_t *ogg_d = demuxer->priv;
    ogg_sync_state *sync = &ogg_d->sync;
    ogg_page* page= &ogg_d->page;
    ogg_stream_state *oss;
    ogg_stream_t *os;
    demux_stream_t *ds;
    ogg_packet op;
    double rate;
    int i, sp, first, precision = 1, do_seek = 1;
    vorbis_info *vi = NULL;
    int64_t gp = 0, old_gp;
    off_t pos, old_pos;
    int np;
    int is_gp_valid;
    double pts;
    int is_keyframe;
    int samplesize = 1;
    ogg_int64_t granulepos_orig;

    if (demuxer->video->id >= 0) {
        ds   = demuxer->video;
        rate = ogg_d->subs[ds->id].samplerate;
    } else {
        ds         = demuxer->audio;
        os         = &ogg_d->subs[ds->id];
        vi         = &(os->vi);
        rate       = vi->rate;
        samplesize = ((sh_audio_t*)ds->sh)->samplesize;
    }

    os  = &ogg_d->subs[ds->id];
    oss = &os->stream;

    old_gp  = os->lastpos;
    old_pos = ogg_d->pos;

    //calculate the granulepos to seek to
    gp = flags & SEEK_ABSOLUTE ? ogg_d->initial_granulepos : os->lastpos;
    if (flags & SEEK_FACTOR) {
        if (ogg_d->duration > 0)
            gp += ogg_d->duration * rel_seek_secs;
        else
            gp += rel_seek_secs * (demuxer->movi_end - demuxer->movi_start) * os->lastpos / ogg_d->pos;
    } else
        gp += rel_seek_secs * rate;
    if (gp < 0)
        gp = 0;

    //calculate the filepos to seek to
    if (ogg_d->syncpoints) {
        for (sp = 0; sp < ogg_d->num_syncpoint; sp++)
            if (ogg_d->syncpoints[sp].granulepos >= gp)
                break;

        if (sp >= ogg_d->num_syncpoint)
            return;
        if (sp > 0 && ogg_d->syncpoints[sp].granulepos - gp > gp - ogg_d->syncpoints[sp - 1].granulepos)
            sp--;
        if (ogg_d->syncpoints[sp].granulepos == os->lastpos) {
            if (sp > 0 && gp < os->lastpos)
                sp--;
            if (sp < ogg_d->num_syncpoint - 1 && gp > os->lastpos)
                sp++;
        }
        pos = ogg_d->syncpoints[sp].page_pos;
        precision = 0;
    } else {
        pos = flags & SEEK_ABSOLUTE ? 0 : ogg_d->pos;
        if (flags & SEEK_FACTOR)
            pos += (demuxer->movi_end - demuxer->movi_start) * rel_seek_secs;
        else {
            if (ogg_d->duration > 0) {
                pos += rel_seek_secs * (demuxer->movi_end - demuxer->movi_start) / (ogg_d->duration / rate);
            } else if (os->lastpos > 0) {
                pos += rel_seek_secs * ogg_d->pos / (os->lastpos / rate);
            }
        }
        if (pos < 0)
            pos = 0;
        if (pos > (demuxer->movi_end - demuxer->movi_start))
            pos = demuxer->movi_end - demuxer->movi_start;
    } // if (ogg_d->syncpoints)

    while (1) {
        if (do_seek) {
            stream_seek(demuxer->stream, pos+demuxer->movi_start);
            ogg_sync_reset(sync);
            for (i = 0; i < ogg_d->num_sub; i++) {
                ogg_stream_reset(&ogg_d->subs[i].stream);
                ogg_d->subs[i].lastpos = ogg_d->subs[i].lastsize = 0;
            }
            ogg_d->pos = pos;
            ogg_d->last_size = 0;
            /* we just guess that we reached correct granulepos, in case a
               subsequent search occurs before we read a valid granulepos */
            os->lastpos = gp;
            first = !(ogg_d->syncpoints);
            do_seek=0;
        }
        ogg_d->pos += ogg_d->last_size;
        ogg_d->last_size = 0;
        np = ogg_sync_pageseek(sync, page);

        if (np < 0)
            ogg_d->pos -= np;
        if (np <= 0) { // We need more data
            char *buf = ogg_sync_buffer(sync, BLOCK_SIZE);
            int len = stream_read(demuxer->stream, buf, BLOCK_SIZE);

            if (len == 0 && demuxer->stream->eof) {
                mp_msg(MSGT_DEMUX, MSGL_V, "EOF while trying to seek !!!!\n");
                return;
            }
            ogg_sync_wrote(sync, len);
            continue;
        }
        ogg_d->last_size = np;
        if (ogg_page_serialno(page) != oss->serialno)
            continue;

        if (ogg_stream_pagein(oss, page) != 0)
            continue;

        while (1) {
            np = ogg_stream_packetout(oss, &op);
            if (np < 0)
                continue;
            else if (np == 0)
                break;
            if (first) { /* Discard the first packet as it's probably broken,
                            and we don't have any other means to decide whether it is
                            complete or not. */
                first = 0;
                break;
            }
            is_gp_valid = (op.granulepos >= 0);
            granulepos_orig=op.granulepos;
            demux_ogg_read_packet(os, &op, &pts, &is_keyframe, samplesize);
            if (precision && is_gp_valid) {
                precision--;
                if (abs(gp - op.granulepos) > rate && (op.granulepos != old_gp)) {
                    //prepare another seek because we are off by more than 1s
                    pos += (gp - op.granulepos) * (pos - old_pos) / (op.granulepos - old_gp);
                    if (pos < 0)
                        pos = 0;
                    if (pos < demuxer->movi_end - demuxer->movi_start) {
                        do_seek=1;
                        break;
                    }
                }
            }
            if (is_gp_valid && pos > 0 && old_gp > gp
                    && 2 * (old_gp - op.granulepos) < old_gp - gp) {
                /* prepare another seek because looking for a syncpoint
                   destroyed the backward search */
                pos = old_pos - 1.5 * (old_pos - pos);
                if (pos < 0)
                    pos = 0;
                if (pos < demuxer->movi_end - demuxer->movi_start) {
                    do_seek=1;
                    break;
                }
            }
            if (!precision && (is_keyframe || os->vorbis || os->speex || os->opus)) {
                if (sub_clear_text(&ogg_sub, MP_NOPTS_VALUE)) {
                    vo_sub = &ogg_sub;
                    vo_osd_changed(OSDTYPE_SUBTITLE);
                }
                op.granulepos=granulepos_orig;
                demux_ogg_add_packet(ds, os, ds->id, &op);
                return;
            }
        }
    }

    mp_msg(MSGT_DEMUX, MSGL_ERR, "Can't find the good packet :(\n");
}

static void demux_close_ogg(demuxer_t *demuxer)
{
    ogg_demuxer_t *ogg_d = demuxer->priv;
    ogg_stream_t *os = NULL;
    int i;

    if (!ogg_d)
        return;

#ifdef CONFIG_ICONV
    subcp_close();
#endif

    ogg_sync_clear(&ogg_d->sync);
    if (ogg_d->subs) {
        for (i = 0; i < ogg_d->num_sub; i++) {
            os = &ogg_d->subs[i];
            ogg_stream_clear(&os->stream);
            if (os->vi_initialized)
                vorbis_info_clear(&os->vi);
        }
        free(ogg_d->subs);
    }
    free(ogg_d->syncpoints);
    free(ogg_d->text_ids);
    if (ogg_d->text_langs) {
        for (i = 0; i < ogg_d->n_text; i++)
            free(ogg_d->text_langs[i]);
        free(ogg_d->text_langs);
    }
    free(ogg_d);
}

static int demux_ogg_control(demuxer_t *demuxer, int cmd, void *arg)
{
    ogg_demuxer_t *ogg_d = demuxer->priv;
    ogg_stream_t *os;
    double rate;

    if (demuxer->video->id >= 0) {
        os = &ogg_d->subs[demuxer->video->id];
        rate = os->samplerate;
    } else {
        os = &ogg_d->subs[demuxer->audio->id];
        rate = os->vi.rate;
    }

    switch(cmd) {
        case DEMUXER_CTRL_GET_TIME_LENGTH:
            if (ogg_d->duration <= 0)
                return DEMUXER_CTRL_DONTKNOW;
            *(double *)arg = (double)(ogg_d->duration) / rate;
            return DEMUXER_CTRL_GUESS;

        case DEMUXER_CTRL_GET_PERCENT_POS:
            if (ogg_d->duration <= 0)
                return DEMUXER_CTRL_DONTKNOW;
            *(int *)arg = ((os->lastpos - ogg_d->initial_granulepos) * 100) / ogg_d->duration;
            return DEMUXER_CTRL_OK;

        default:
            return DEMUXER_CTRL_NOTIMPL;
    }
}

const demuxer_desc_t demuxer_desc_ogg = {
    "Ogg demuxer",
    "ogg",
    "Ogg",
    "?",
    "",
    DEMUXER_TYPE_OGG,
    1, // safe autodetect
    demux_ogg_open,
    demux_ogg_fill_buffer,
    NULL,
    demux_close_ogg,
    demux_ogg_seek,
    demux_ogg_control
};
