]> git.sesse.net Git - vlc/blobdiff - modules/demux/ogg.c
MKV: Set default value for audio tracks when parsing one
[vlc] / modules / demux / ogg.c
index 2bc1bcaab41ed418616d49afc71a0db181443afb..52bf76bbbfa74a9b6f09675400c3008ad8be0750 100644 (file)
@@ -1,25 +1,25 @@
 /*****************************************************************************
  * ogg.c : ogg stream demux module for vlc
  *****************************************************************************
- * Copyright (C) 2001-2007 the VideoLAN team
+ * Copyright (C) 2001-2007 VLC authors and VideoLAN
  * $Id$
  *
  * Authors: Gildas Bazin <gbazin@netcourrier.com>
  *          Andre Pang <Andre.Pang@csiro.au> (Annodex support)
  *
- * This program 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
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
  * (at your option) any later version.
  *
  * This program 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.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
 /*****************************************************************************
@@ -123,6 +123,7 @@ static int  Control( demux_t *, int, va_list );
 static int  Ogg_ReadPage     ( demux_t *, ogg_page * );
 static void Ogg_UpdatePCR    ( logical_stream_t *, ogg_packet * );
 static void Ogg_DecodePacket ( demux_t *, logical_stream_t *, ogg_packet * );
+static int  Ogg_OpusPacketDuration( logical_stream_t *, ogg_packet * );
 
 static int Ogg_BeginningOfStream( demux_t *p_demux );
 static int Ogg_FindLogicalStreams( demux_t *p_demux );
@@ -134,12 +135,13 @@ static bool Ogg_LogicalStreamResetEsFormat( demux_t *p_demux, logical_stream_t *
 
 /* */
 static void Ogg_ExtractMeta( demux_t *p_demux, vlc_fourcc_t i_codec, const uint8_t *p_headers, int i_headers );
+static int64_t Ogg_GetLastPacket( demux_t *p_demux, logical_stream_t *p_stream, ogg_packet *p_oggpacket, double f_rate );
 
 /* Logical bitstream headers */
 static void Ogg_ReadTheoraHeader( demux_t *, logical_stream_t *, ogg_packet * );
-static void Ogg_ReadVorbisHeader( logical_stream_t *, ogg_packet * );
+static void Ogg_ReadVorbisHeader( demux_t *, logical_stream_t *, ogg_packet * );
 static void Ogg_ReadSpeexHeader( logical_stream_t *, ogg_packet * );
-static void Ogg_ReadOpusHeader( logical_stream_t *, ogg_packet * );
+static void Ogg_ReadOpusHeader( demux_t *, logical_stream_t *, ogg_packet * );
 static void Ogg_ReadKateHeader( logical_stream_t *, ogg_packet * );
 static void Ogg_ReadFlacHeader( demux_t *, logical_stream_t *, ogg_packet * );
 static void Ogg_ReadAnnodexHeader( demux_t *, logical_stream_t *, ogg_packet * );
@@ -186,6 +188,7 @@ static int Open( vlc_object_t * p_this )
 
     /* */
     p_sys->p_meta = NULL;
+    TAB_INIT( p_sys->i_seekpoints, p_sys->pp_seekpoints );
 
     return VLC_SUCCESS;
 }
@@ -206,6 +209,8 @@ static void Close( vlc_object_t *p_this )
     if( p_sys->p_old_stream )
         Ogg_LogicalStreamDelete( p_demux, p_sys->p_old_stream );
 
+    TAB_CLEAN( p_sys->i_seekpoints, p_sys->pp_seekpoints );
+
     free( p_sys );
 }
 
@@ -289,6 +294,7 @@ static int Demux( demux_t * p_demux )
                 p_stream->b_reinit = true;
                 p_stream->i_pcr = VLC_TS_0;
                 p_stream->i_interpolated_pcr = VLC_TS_0;
+                p_stream->i_previous_granulepos = -1;
                 es_out_Control( p_demux->out, ES_OUT_SET_PCR, VLC_TS_0);
             }
 
@@ -315,7 +321,7 @@ static int Demux( demux_t * p_demux )
                         oggpacket.bytes >= 7 &&
                         ! memcmp( oggpacket.packet, "\x01vorbis", 7 ) )
                 {
-                    Ogg_ReadVorbisHeader( p_stream, &oggpacket );
+                    Ogg_ReadVorbisHeader( p_demux, p_stream, &oggpacket );
                     p_stream->i_secondary_header_packets = 0;
                 }
                 else if( p_stream->fmt.i_codec == VLC_CODEC_CMML )
@@ -342,10 +348,37 @@ static int Demux( demux_t * p_demux )
                 if( p_stream->i_pcr >= 0 )
                 {
                     p_stream->b_reinit = false;
+                    /* For Opus, trash the first 80 ms of decoded output as
+                       well, to avoid blowing out speakers if we get unlucky.
+                       Opus predicts content from prior frames, which can go
+                       badly if we seek right where the stream goes from very
+                       quiet to very loud. It will converge after a bit. */
+                    if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+                    {
+                        ogg_int64_t start_time;
+                        int duration;
+                        p_stream->i_skip_frames = 80*48;
+                        /* Make sure we never play audio from within the
+                           pre-skip at the beginning of the stream. */
+                        duration =
+                            Ogg_OpusPacketDuration( p_stream, &oggpacket );
+                        start_time = p_stream->i_previous_granulepos;
+                        if( duration > 0 )
+                        {
+                            start_time = start_time > duration ?
+                                start_time - duration : 0;
+                        }
+                        if( p_stream->i_pre_skip > start_time )
+                        {
+                            p_stream->i_skip_frames +=
+                                p_stream->i_pre_skip - start_time;
+                        }
+                    }
                 }
                 else
                 {
                     p_stream->i_interpolated_pcr = -1;
+                    p_stream->i_previous_granulepos = -1;
                     continue;
                 }
 
@@ -463,6 +496,7 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
                 p_stream->b_reinit = true;
                 p_stream->i_pcr = -1;
                 p_stream->i_interpolated_pcr = -1;
+                p_stream->i_previous_granulepos = -1;
                 ogg_stream_reset( &p_stream->os );
             }
             ogg_sync_reset( &p_sys->oy );
@@ -476,6 +510,41 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
             *pi64 = p_sys->i_length * 1000000;
             return VLC_SUCCESS;
 
+        case DEMUX_GET_TITLE_INFO:
+        {
+            input_title_t ***ppp_title = (input_title_t***)va_arg( args, input_title_t*** );
+            int *pi_int    = (int*)va_arg( args, int* );
+            int *pi_title_offset = (int*)va_arg( args, int* );
+            int *pi_seekpoint_offset = (int*)va_arg( args, int* );
+
+            if( p_sys->i_seekpoints > 0 )
+            {
+                *pi_int = 1;
+                *ppp_title = malloc( sizeof( input_title_t**) );
+                input_title_t *p_title = (*ppp_title)[0] = vlc_input_title_New();
+                for( int i = 0; i < p_sys->i_seekpoints; i++ )
+                {
+                    TAB_APPEND( p_title->i_seekpoint, p_title->seekpoint, p_sys->pp_seekpoints[i] );
+                }
+                *pi_title_offset = 0;
+                *pi_seekpoint_offset = 0;
+            }
+            return VLC_SUCCESS;
+        }
+        case DEMUX_SET_TITLE:
+        {
+            const int i_title = (int)va_arg( args, int );
+            if( i_title > 1 )
+                return VLC_EGENERIC;
+            return VLC_SUCCESS;
+        }
+        case DEMUX_SET_SEEKPOINT:
+        {
+            const int i_seekpoint = (int)va_arg( args, int );
+            if( i_seekpoint > p_sys->i_seekpoints )
+                return VLC_EGENERIC;
+            return VLC_EGENERIC;// Seek( p_demux, p_sys->pp_seekpoints[i_seekpoint]->i_time_offset );
+        }
 
         default:
             return demux_vaControlHelper( p_demux->s, 0, -1, p_sys->i_bitrate,
@@ -516,6 +585,7 @@ static int Ogg_ReadPage( demux_t *p_demux, ogg_page *p_oggpage )
 static void Ogg_UpdatePCR( logical_stream_t *p_stream,
                            ogg_packet *p_oggpacket )
 {
+    p_stream->i_end_trim = 0;
     /* Convert the granulepos into a pcr */
     if( p_oggpacket->granulepos >= 0 )
     {
@@ -538,15 +608,35 @@ static void Ogg_UpdatePCR( logical_stream_t *p_stream,
         }
         else
         {
-            p_stream->i_pcr = p_oggpacket->granulepos * INT64_C(1000000)
-                              / p_stream->f_rate;
+            ogg_int64_t sample;
+            sample = p_oggpacket->granulepos;
+            if( p_oggpacket->e_o_s &&
+                p_stream->fmt.i_codec == VLC_CODEC_OPUS &&
+                p_stream->i_previous_granulepos >= 0 )
+            {
+                int duration;
+                duration = Ogg_OpusPacketDuration( p_stream, p_oggpacket );
+                if( duration > 0 )
+                {
+                    ogg_int64_t end_sample;
+                    end_sample = p_stream->i_previous_granulepos + duration;
+                    if( end_sample > sample )
+                        p_stream->i_end_trim = (int)(end_sample - sample);
+                }
+            }
+            if (sample >= p_stream->i_pre_skip)
+                sample -= p_stream->i_pre_skip;
+            else
+                sample = 0;
+            p_stream->i_pcr = sample * INT64_C(1000000) / p_stream->f_rate;
         }
 
-        p_stream->i_pcr += 1;
+        p_stream->i_pcr += VLC_TS_0;
         p_stream->i_interpolated_pcr = p_stream->i_pcr;
     }
     else
     {
+        int duration;
         p_stream->i_pcr = -1;
 
         /* no granulepos available, try to interpolate the pcr.
@@ -554,11 +644,30 @@ static void Ogg_UpdatePCR( logical_stream_t *p_stream,
         if( p_stream->fmt.i_cat == VIDEO_ES )
             /* 1 frame per packet */
             p_stream->i_interpolated_pcr += (INT64_C(1000000) / p_stream->f_rate);
+        else if( p_stream->fmt.i_codec == VLC_CODEC_OPUS &&
+                 p_stream->i_previous_granulepos >= 0 &&
+                 ( duration =
+                     Ogg_OpusPacketDuration( p_stream, p_oggpacket ) ) > 0 )
+        {
+            ogg_int64_t sample;
+            p_oggpacket->granulepos =
+                p_stream->i_previous_granulepos + duration;
+            sample = p_oggpacket->granulepos;
+            if (sample >= p_stream->i_pre_skip)
+                sample -= p_stream->i_pre_skip;
+            else
+                sample = 0;
+            p_stream->i_interpolated_pcr =
+                VLC_TS_0 + sample * INT64_C(1000000) / p_stream->f_rate;
+        }
         else if( p_stream->fmt.i_bitrate )
+        {
             p_stream->i_interpolated_pcr +=
                 ( p_oggpacket->bytes * INT64_C(1000000) /
                   p_stream->fmt.i_bitrate / 8 );
+        }
     }
+    p_stream->i_previous_granulepos = p_oggpacket->granulepos;
 }
 
 /****************************************************************************
@@ -574,13 +683,6 @@ static void Ogg_DecodePacket( demux_t *p_demux,
     mtime_t i_pts = -1, i_interpolated_pts;
     demux_sys_t *p_ogg = p_demux->p_sys;
 
-    /* Sanity check */
-    if( !p_oggpacket->bytes )
-    {
-        msg_Dbg( p_demux, "discarding 0 sized packet" );
-        return;
-    }
-
     if( p_oggpacket->bytes >= 7 &&
         ! memcmp ( p_oggpacket->packet, "Annodex", 7 ) )
     {
@@ -594,7 +696,7 @@ static void Ogg_DecodePacket( demux_t *p_demux,
         return;
     }
 
-    if( p_stream->fmt.i_codec == VLC_CODEC_SUBT &&
+    if( p_stream->fmt.i_codec == VLC_CODEC_SUBT && p_oggpacket->bytes > 0 &&
         p_oggpacket->packet[0] & PACKET_TYPE_BITS ) return;
 
     /* Check the ES is selected */
@@ -709,17 +811,6 @@ static void Ogg_DecodePacket( demux_t *p_demux,
         p_stream->fmt.i_codec == VLC_CODEC_SPEEX ||
         p_stream->fmt.i_codec == VLC_CODEC_OPUS ||
         p_stream->fmt.i_codec == VLC_CODEC_FLAC )
-        if (p_stream->i_pre_skip)
-        {
-            if( i_pts > p_stream->i_pre_skip )
-            {
-                i_pts -= - p_stream->i_pre_skip;
-            }
-            else
-            {
-                i_pts = 0;
-            }
-        }
     {
         if( p_stream->i_pcr >= 0 )
         {
@@ -738,17 +829,6 @@ static void Ogg_DecodePacket( demux_t *p_demux,
 
             /* The granulepos is the end date of the sample */
             i_pts = p_stream->i_pcr;
-            if (p_stream->i_pre_skip)
-            {
-                if( i_pts > p_stream->i_pre_skip )
-                {
-                    i_pts -= - p_stream->i_pre_skip;
-                }
-                else
-                {
-                    i_pts = 0;
-                }
-            }
         }
     }
 
@@ -792,27 +872,59 @@ static void Ogg_DecodePacket( demux_t *p_demux,
         return;
     }
 
-    if( p_oggpacket->bytes <= 0 )
-        return;
-
     if( !( p_block = block_New( p_demux, p_oggpacket->bytes ) ) ) return;
 
 
-    /* may need to preroll video frames after a seek */
+    /* may need to preroll after a seek */
     if ( p_stream->i_skip_frames > 0 )
     {
-        p_block->i_flags |= BLOCK_FLAG_PREROLL;
-        p_stream->i_skip_frames--;
+        if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+        {
+            int duration;
+            duration = Ogg_OpusPacketDuration( p_stream, p_oggpacket );
+            if( p_stream->i_skip_frames > duration )
+            {
+                p_block->i_flags |= BLOCK_FLAG_PREROLL;
+                p_block->i_nb_samples = 0;
+                p_stream->i_skip_frames -= duration;
+            }
+            else
+            {
+                p_block->i_nb_samples = duration - p_stream->i_skip_frames;
+                if( p_stream->i_previous_granulepos >=
+                    p_block->i_nb_samples + p_stream->i_pre_skip )
+                {
+                    i_pts = VLC_TS_0 + (p_stream->i_previous_granulepos
+                        - p_block->i_nb_samples - p_stream->i_pre_skip) *
+                        INT64_C(1000000) / p_stream->f_rate;
+                }
+                p_stream->i_skip_frames = 0;
+            }
+        }
+        else
+        {
+            p_block->i_flags |= BLOCK_FLAG_PREROLL;
+            p_stream->i_skip_frames--;
+        }
     }
+    else if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+        p_block->i_nb_samples = Ogg_OpusPacketDuration( p_stream, p_oggpacket );
 
 
     /* Normalize PTS */
-    if( i_pts == 0 ) i_pts = VLC_TS_0;
-    else if( i_pts == -1 && i_interpolated_pts == 0 ) i_pts = VLC_TS_0;
+    if( i_pts == VLC_TS_INVALID ) i_pts = VLC_TS_0;
+    else if( i_pts == -1 && i_interpolated_pts == VLC_TS_INVALID )
+        i_pts = VLC_TS_0;
+    else if( i_pts == -1 && p_stream->fmt.i_cat == VIDEO_ES )
+        i_pts = i_interpolated_pts;
     else if( i_pts == -1 ) i_pts = VLC_TS_INVALID;
 
     if( p_stream->fmt.i_cat == AUDIO_ES )
+    {
         p_block->i_dts = p_block->i_pts = i_pts;
+        /* Blatant abuse of the i_length field. */
+        p_block->i_length = p_stream->i_end_trim;
+    }
     else if( p_stream->fmt.i_cat == SPU_ES )
     {
         p_block->i_dts = p_block->i_pts = i_pts;
@@ -857,6 +969,12 @@ static void Ogg_DecodePacket( demux_t *p_demux,
         p_stream->fmt.i_codec != VLC_CODEC_DIRAC &&
         p_stream->fmt.i_codec != VLC_CODEC_KATE )
     {
+        if( p_oggpacket->bytes <= 0 )
+        {
+            msg_Dbg( p_demux, "discarding 0 sized packet" );
+            block_Release( p_block );
+            return;
+        }
         /* We remove the header from the packet */
         i_header_len = (*p_oggpacket->packet & PACKET_LEN_BITS01) >> 6;
         i_header_len |= (*p_oggpacket->packet & PACKET_LEN_BITS2) << 1;
@@ -905,6 +1023,47 @@ static void Ogg_DecodePacket( demux_t *p_demux,
     es_out_Send( p_demux->out, p_stream->p_es, p_block );
 }
 
+/* Re-implemented to avoid linking against libopus from the demuxer. */
+static int Ogg_OpusPacketDuration( logical_stream_t *p_stream,
+                                   ogg_packet *p_oggpacket )
+{
+    static const int silk_fs_div[4] = { 6000, 3000, 1500, 1000 };
+    int toc;
+    int nframes;
+    int frame_size;
+    int nsamples;
+    int i_rate;
+    if( p_oggpacket->bytes < 1 )
+        return VLC_EGENERIC;
+    toc = p_oggpacket->packet[0];
+    switch( toc&3 )
+    {
+        case 0:
+            nframes = 1;
+            break;
+        case 1:
+        case 2:
+            nframes = 2;
+            break;
+        default:
+            if( p_oggpacket->bytes < 2 )
+                return VLC_EGENERIC;
+            nframes = p_oggpacket->packet[1]&0x3F;
+            break;
+    }
+    i_rate = (int)p_stream->fmt.audio.i_rate;
+    if( toc&0x80 )
+        frame_size = (i_rate << (toc >> 3 & 3)) / 400;
+    else if( ( toc&0x60 ) == 0x60 )
+        frame_size = i_rate/(100 >> (toc >> 3 & 1));
+    else
+        frame_size = i_rate*60 / silk_fs_div[toc >> 3 & 3];
+    nsamples = nframes*frame_size;
+    if( nsamples*25 > i_rate*3 )
+        return VLC_EGENERIC;
+    return nsamples;
+}
+
 /****************************************************************************
  * Ogg_FindLogicalStreams: Find the logical streams embedded in the physical
  *                         stream and fill p_ogg.
@@ -976,7 +1135,7 @@ static int Ogg_FindLogicalStreams( demux_t *p_demux )
                 if( oggpacket.bytes >= 7 &&
                     ! memcmp( oggpacket.packet, "\x01vorbis", 7 ) )
                 {
-                    Ogg_ReadVorbisHeader( p_stream, &oggpacket );
+                    Ogg_ReadVorbisHeader( p_demux, p_stream, &oggpacket );
                     msg_Dbg( p_demux, "found vorbis header" );
                 }
                 /* Check for Speex header */
@@ -993,11 +1152,12 @@ static int Ogg_FindLogicalStreams( demux_t *p_demux )
                 else if( oggpacket.bytes >= 8 &&
                     ! memcmp( oggpacket.packet, "OpusHead", 8 ) )
                 {
-                    Ogg_ReadOpusHeader( p_stream, &oggpacket );
+                    Ogg_ReadOpusHeader( p_demux, p_stream, &oggpacket );
                     msg_Dbg( p_demux, "found opus header, channels: %i, "
                              "pre-skip: %i",
                              p_stream->fmt.audio.i_channels,
                              (int)p_stream->i_pre_skip);
+                    p_stream->i_skip_frames = p_stream->i_pre_skip;
                 }
                 /* Check for Flac header (< version 1.1.1) */
                 else if( oggpacket.bytes >= 4 &&
@@ -1203,8 +1363,8 @@ static int Ogg_FindLogicalStreams( demux_t *p_demux )
                         p_ogg->i_streams--;
                     }
                 }
-                else if( (*oggpacket.packet & PACKET_TYPE_BITS ) == PACKET_TYPE_HEADER &&
-                         oggpacket.bytes >= 44+1 )
+                else if( oggpacket.bytes >= 44+1 &&
+                         (*oggpacket.packet & PACKET_TYPE_BITS ) == PACKET_TYPE_HEADER )
                 {
                     stream_header_t tmp;
                     stream_header_t *st = &tmp;
@@ -1546,15 +1706,102 @@ static bool Ogg_IsVorbisFormatCompatible( const es_format_t *p_new, const es_for
         free( pp_old_data[i] );
     return b_match;
 }
+
+static bool Ogg_IsOpusFormatCompatible( const es_format_t *p_new,
+                                        const es_format_t *p_old )
+{
+    unsigned pi_new_size[XIPH_MAX_HEADER_COUNT];
+    void     *pp_new_data[XIPH_MAX_HEADER_COUNT];
+    unsigned i_new_count;
+    if( xiph_SplitHeaders(pi_new_size, pp_new_data, &i_new_count, p_new->i_extra, p_new->p_extra ) )
+        i_new_count = 0;
+    unsigned pi_old_size[XIPH_MAX_HEADER_COUNT];
+    void     *pp_old_data[XIPH_MAX_HEADER_COUNT];
+    unsigned i_old_count;
+    if( xiph_SplitHeaders(pi_old_size, pp_old_data, &i_old_count, p_old->i_extra, p_old->p_extra ) )
+        i_old_count = 0;
+    bool b_match = false;
+    if( i_new_count == i_old_count && i_new_count > 0 )
+    {
+        static const unsigned char default_map[2] = { 0, 1 };
+        unsigned char *p_old_head;
+        unsigned char *p_new_head;
+        const unsigned char *p_old_map;
+        const unsigned char *p_new_map;
+        int i_old_channel_count;
+        int i_new_channel_count;
+        int i_old_stream_count;
+        int i_new_stream_count;
+        int i_old_coupled_count;
+        int i_new_coupled_count;
+        p_old_head = (unsigned char *)pp_old_data[0];
+        i_old_channel_count = i_old_stream_count = i_old_coupled_count = 0;
+        p_old_map = default_map;
+        if( pi_old_size[0] >= 19 && p_old_head[8] <= 15 )
+        {
+            i_old_channel_count = p_old_head[9];
+            switch( p_old_head[18] )
+            {
+                case 0:
+                    i_old_stream_count = 1;
+                    i_old_coupled_count = i_old_channel_count - 1;
+                    break;
+                case 1:
+                    if( pi_old_size[0] >= 21U + i_old_channel_count )
+                    {
+                        i_old_stream_count = p_old_head[19];
+                        i_old_coupled_count = p_old_head[20];
+                        p_old_map = p_old_head + 21;
+                    }
+                    break;
+            }
+        }
+        p_new_head = (unsigned char *)pp_new_data[0];
+        i_new_channel_count = i_new_stream_count = i_new_coupled_count = 0;
+        p_new_map = default_map;
+        if( pi_new_size[0] >= 19 && p_new_head[8] <= 15 )
+        {
+            i_new_channel_count = p_new_head[9];
+            switch( p_new_head[18] )
+            {
+                case 0:
+                    i_new_stream_count = 1;
+                    i_new_coupled_count = i_new_channel_count - 1;
+                    break;
+                case 1:
+                    if( pi_new_size[0] >= 21U + i_new_channel_count )
+                    {
+                        i_new_stream_count = p_new_head[19];
+                        i_new_coupled_count = p_new_head[20];
+                        p_new_map = p_new_head+21;
+                    }
+                    break;
+            }
+        }
+        b_match = i_old_channel_count == i_new_channel_count &&
+                  i_old_stream_count == i_new_stream_count &&
+                  i_old_coupled_count == i_new_coupled_count &&
+                  memcmp(p_old_map, p_new_map,
+                      i_new_channel_count*sizeof(*p_new_map)) == 0;
+    }
+    for( unsigned i = 0; i < i_new_count; i++ )
+        free( pp_new_data[i] );
+    for( unsigned i = 0; i < i_old_count; i++ )
+        free( pp_old_data[i] );
+    return b_match;
+}
+
 static bool Ogg_LogicalStreamResetEsFormat( demux_t *p_demux, logical_stream_t *p_stream )
 {
     bool b_compatible = false;
     if( !p_stream->fmt_old.i_cat || !p_stream->fmt_old.i_codec )
         return true;
 
-    /* Only vorbis is supported */
+    /* Only Vorbis and Opus are supported. */
     if( p_stream->fmt.i_codec == VLC_CODEC_VORBIS )
         b_compatible = Ogg_IsVorbisFormatCompatible( &p_stream->fmt, &p_stream->fmt_old );
+    else if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
+        b_compatible = Ogg_IsOpusFormatCompatible( &p_stream->fmt, &p_stream->fmt_old );
 
     if( !b_compatible )
         msg_Warn( p_demux, "cannot reuse old stream, resetting the decoder" );
@@ -1574,7 +1821,13 @@ static void Ogg_ExtractXiphMeta( demux_t *p_demux, const void *p_headers, unsign
     /* TODO how to handle multiple comments properly ? */
     if( i_count >= 2 && pi_size[1] > i_skip )
         vorbis_ParseComment( &p_ogg->p_meta, (uint8_t*)pp_data[1] + i_skip, pi_size[1] - i_skip,
-                             &p_ogg->i_attachments, &p_ogg->attachments );
+                             &p_ogg->i_attachments, &p_ogg->attachments,
+                             &p_ogg->i_seekpoints, &p_ogg->pp_seekpoints );
+
+    if( p_ogg->i_seekpoints > 1 )
+    {
+        p_demux->info.i_update |= INPUT_UPDATE_TITLE_LIST;
+    }
 
     for( unsigned i = 0; i < i_count; i++ )
         free( pp_data[i] );
@@ -1587,15 +1840,13 @@ static void Ogg_ExtractMeta( demux_t *p_demux, vlc_fourcc_t i_codec, const uint8
     {
     /* 3 headers with the 2° one being the comments */
     case VLC_CODEC_VORBIS:
-        Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 1+6 );
-        break;
     case VLC_CODEC_THEORA:
         Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 1+6 );
         break;
-    case VLC_CODEC_SPEEX:
-        Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 0 );
-        break;
     case VLC_CODEC_OPUS:
+        Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 8 );
+        break;
+    case VLC_CODEC_SPEEX:
         Ogg_ExtractXiphMeta( p_demux, p_headers, i_headers, 0 );
         break;
 
@@ -1620,6 +1871,19 @@ static void Ogg_ExtractMeta( demux_t *p_demux, vlc_fourcc_t i_codec, const uint8
         p_demux->info.i_update |= INPUT_UPDATE_META;
 }
 
+static int64_t Ogg_GetLastPacket( demux_t *p_demux, logical_stream_t *p_stream,
+                                  ogg_packet *p_oggpacket, double f_rate )
+{
+    int64_t last_packet = oggseek_get_last_frame( p_demux, p_stream );
+    /*
+     * Since there's quite a good chance that ogg_stream_packetout was called,
+     * the given p_oggpacket may point to invalid data. Fill it with some valid ones
+     */
+    ogg_stream_packetpeek( &p_stream->os, p_oggpacket );
+
+    return ( last_packet >= 0 ) ? last_packet / f_rate : -1;
+}
+
 static void Ogg_ReadTheoraHeader( demux_t *p_demux, logical_stream_t *p_stream,
                                   ogg_packet *p_oggpacket )
 {
@@ -1680,6 +1944,7 @@ static void Ogg_ReadTheoraHeader( demux_t *p_demux, logical_stream_t *p_stream,
 
     i_version = i_major * 1000000 + i_minor * 1000 + i_subminor;
     p_stream->i_keyframe_offset = 0;
+    p_stream->f_rate = ((float)i_fps_numerator) / i_fps_denominator;
 
     if ( i_version >= 3002001 )
     {
@@ -1687,24 +1952,14 @@ static void Ogg_ReadTheoraHeader( demux_t *p_demux, logical_stream_t *p_stream,
     }
     if ( p_demux->p_sys->i_length < 0 )
     {
-        int64_t last_frame = oggseek_get_last_frame( p_demux, p_stream );
-        /*
-         * Since there's quite a good chance that ogg_stream_packetout was called,
-         * the given p_oggpacket may point to invalid data. Fill it with some valid ones
-         */
-        ogg_stream_packetpeek( &p_stream->os, p_oggpacket );
-
-        if ( last_frame >= 0 )
-        {
-            p_demux->p_sys->i_length = last_frame / ((float)i_fps_numerator /
-                                                     (float)i_fps_denominator);
-        }
+        int64_t last_packet = Ogg_GetLastPacket( p_demux, p_stream, p_oggpacket, p_stream->f_rate );
+        if ( last_packet >= 0 )
+            p_demux->p_sys->i_length = last_packet;
     }
 
-    p_stream->f_rate = ((float)i_fps_numerator) / i_fps_denominator;
 }
 
-static void Ogg_ReadVorbisHeader( logical_stream_t *p_stream,
+static void Ogg_ReadVorbisHeader( demux_t *p_demux, logical_stream_t *p_stream,
                                   ogg_packet *p_oggpacket )
 {
     oggpack_buffer opb;
@@ -1725,6 +1980,13 @@ static void Ogg_ReadVorbisHeader( logical_stream_t *p_stream,
         oggpack_read( &opb, 32 );
     oggpack_adv( &opb, 32 );
     p_stream->fmt.i_bitrate = oggpack_read( &opb, 32 );
+
+    if ( p_demux->p_sys->i_length < 0 )
+    {
+        int64_t last_packet = Ogg_GetLastPacket( p_demux, p_stream, p_oggpacket, p_stream->f_rate );
+        if ( last_packet >= 0 )
+            p_demux->p_sys->i_length = last_packet;
+    }
 }
 
 static void Ogg_ReadSpeexHeader( logical_stream_t *p_stream,
@@ -1752,8 +2014,9 @@ static void Ogg_ReadSpeexHeader( logical_stream_t *p_stream,
     p_stream->fmt.i_bitrate = oggpack_read( &opb, 32 );
 }
 
-static void Ogg_ReadOpusHeader( logical_stream_t *p_stream,
-                                 ogg_packet *p_oggpacket )
+static void Ogg_ReadOpusHeader( demux_t *p_demux,
+                                logical_stream_t *p_stream,
+                                ogg_packet *p_oggpacket )
 {
     oggpack_buffer opb;
 
@@ -1770,12 +2033,19 @@ static void Ogg_ReadOpusHeader( logical_stream_t *p_stream,
     p_stream->f_rate = p_stream->fmt.audio.i_rate = 48000;
     p_stream->fmt.i_bitrate = 0;
 
-    /* Cheat and get additionnal info ;) */
+    /* Cheat and get additional info ;) */
     oggpack_readinit( &opb, p_oggpacket->packet, p_oggpacket->bytes);
     oggpack_adv( &opb, 64 );
     oggpack_adv( &opb, 8 ); /* version_id */
     p_stream->fmt.audio.i_channels = oggpack_read( &opb, 8 );
     p_stream->i_pre_skip = oggpack_read( &opb, 16 );
+
+    if ( p_demux->p_sys->i_length < 0 )
+    {
+        int64_t last_packet = Ogg_GetLastPacket( p_demux, p_stream, p_oggpacket, p_stream->f_rate );
+        if ( last_packet >= 0 )
+            p_demux->p_sys->i_length = last_packet;
+    }
 }
 
 static void Ogg_ReadFlacHeader( demux_t *p_demux, logical_stream_t *p_stream,
@@ -1787,7 +2057,7 @@ static void Ogg_ReadFlacHeader( demux_t *p_demux, logical_stream_t *p_stream,
     bs_init( &s, p_oggpacket->packet, p_oggpacket->bytes );
 
     bs_read( &s, 1 );
-    if( bs_read( &s, 7 ) == 0 )
+    if( p_oggpacket->bytes > 0 && bs_read( &s, 7 ) == 0 )
     {
         if( bs_read( &s, 24 ) >= 34 /*size STREAMINFO*/ )
         {