]> git.sesse.net Git - vlc/commitdiff
Ogg-seek-new-logic-generic-changes
authorG Finch <salsaman@gmail.com>
Thu, 5 Aug 2010 15:11:19 +0000 (12:11 -0300)
committerIlkka Ollakka <ileoo@videolan.org>
Tue, 10 Aug 2010 10:38:46 +0000 (13:38 +0300)
Signed-off-by: Ilkka Ollakka <ileoo@videolan.org>
modules/demux/Modules.am
modules/demux/ogg.c
modules/demux/ogg.h
modules/demux/oggseek.c [new file with mode: 0644]
modules/demux/oggseek.h [new file with mode: 0644]

index 15a19b1278fb7b5ccbf40267eb166f758d126394..88a4da65286c856af1fe47c050d9a74eb6d68016 100644 (file)
@@ -1,6 +1,7 @@
 SUBDIRS = asf avformat avi mkv mp4 mpeg playlist
 SOURCES_flacsys = flac.c
-SOURCES_ogg = ogg.c ogg.h vorbis.h kate_categories.c kate_categories.h xiph.h
+SOURCES_ogg = ogg.c ogg.h oggseek.c oggseek.h vorbis.h kate_categories.c \
+       kate_categories.h xiph.h
 SOURCES_demuxdump = demuxdump.c
 SOURCES_rawdv = rawdv.c
 SOURCES_rawvid = rawvid.c
index 6196ebabf1fd12b1a265e43bed08ca2f64992239..15631fc5632743e475cb8566385258132b0d2059 100644 (file)
@@ -43,6 +43,7 @@
 #include "vorbis.h"
 #include "kate_categories.h"
 #include "ogg.h"
+#include "oggseek.h"
 
 /*****************************************************************************
  * Module descriptor
@@ -217,6 +218,7 @@ static int Demux( demux_t * p_demux )
     ogg_page    oggpage;
     ogg_packet  oggpacket;
     int         i_stream;
+    bool b_skipping = false;
 
 
     if( p_sys->i_eos == p_sys->i_streams )
@@ -290,7 +292,10 @@ static int Demux( demux_t * p_demux )
             }
 
             if( ogg_stream_pagein( &p_stream->os, &oggpage ) != 0 )
+            {
                 continue;
+            }
+
         }
 
         while( ogg_stream_packetout( &p_stream->os, &oggpacket ) > 0 )
@@ -316,8 +321,17 @@ static int Demux( demux_t * p_demux )
                 {
                     p_stream->i_secondary_header_packets = 0;
                 }
+
+                /* update start of data pointer */
+                p_stream->i_data_start = stream_Tell( p_demux->s );
+
             }
 
+            /* If any streams have i_skip_frames, only decode (pre-roll)
+             *  for those streams */
+            if ( b_skipping && p_stream->i_skip_frames == 0 ) continue;
+
+
             if( p_stream->b_reinit )
             {
                 /* If synchro is re-initialized we need to drop all the packets
@@ -376,7 +390,7 @@ static int Demux( demux_t * p_demux )
             p_sys->i_pcr = p_stream->i_interpolated_pcr;
     }
 
-    if( p_sys->i_pcr >= 0 )
+    if( p_sys->i_pcr >= 0 && ! b_skipping )
         es_out_Control( p_demux->out, ES_OUT_SET_PCR, VLC_TS_0 + p_sys->i_pcr );
 
     return 1;
@@ -729,6 +743,15 @@ static void Ogg_DecodePacket( demux_t *p_demux,
 
     if( !( p_block = block_New( p_demux, p_oggpacket->bytes ) ) ) return;
 
+
+    /* may need to preroll video frames after a seek */
+    if ( p_stream->i_skip_frames > 0 )
+    {
+        p_block->i_flags |= BLOCK_FLAG_PREROLL;
+        p_stream->i_skip_frames--;
+    }
+
+
     /* 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;
@@ -759,6 +782,8 @@ static void Ogg_DecodePacket( demux_t *p_demux,
         p_block->i_dts = p_stream->i_pcr;
         p_block->i_pts = VLC_TS_INVALID;
         /* NB, OggDirac granulepos values are in units of 2*picturerate */
+
+        /* granulepos for dirac is possibly broken, this value should be ignored */
         if( -1 != p_oggpacket->granulepos )
             p_block->i_pts = u_pnum * INT64_C(1000000) / p_stream->f_rate / 2;
     }
@@ -842,6 +867,10 @@ static int Ogg_FindLogicalStreams( demux_t *p_demux )
     ogg_page oggpage;
     int i_stream;
 
+    p_ogg->i_total_length = stream_Size ( p_demux->s );
+    msg_Dbg( p_demux, "File length is %"PRId64" bytes", p_ogg->i_total_length );
+
+
     while( Ogg_ReadPage( p_demux, &oggpage ) == VLC_SUCCESS )
     {
         if( ogg_page_bos( &oggpage ) )
@@ -864,6 +893,9 @@ static int Ogg_FindLogicalStreams( demux_t *p_demux )
                 p_stream->i_secondary_header_packets = 0;
 
                 p_stream->i_keyframe_offset = 0;
+                p_stream->i_skip_frames = 0;
+
+                p_stream->i_data_start = 0;
 
                 es_format_Init( &p_stream->fmt, 0, 0 );
                 es_format_Init( &p_stream->fmt_old, 0, 0 );
@@ -1314,6 +1346,9 @@ static int Ogg_BeginningOfStream( demux_t *p_demux )
 
         p_stream->p_es = NULL;
 
+        /* initialise kframe index */
+        p_stream->idx=NULL;
+
         /* Try first to reuse an old ES */
         if( p_old_stream &&
             p_old_stream->fmt.i_cat == p_stream->fmt.i_cat &&
@@ -1359,6 +1394,11 @@ static int Ogg_BeginningOfStream( demux_t *p_demux )
         Ogg_LogicalStreamDelete( p_demux, p_ogg->p_old_stream );
         p_ogg->p_old_stream = NULL;
     }
+
+
+    /* get total frame count for video stream; we will need this for seeking */
+    p_ogg->i_total_frames = 0;
+
     return VLC_SUCCESS;
 }
 
@@ -1399,6 +1439,11 @@ static void Ogg_LogicalStreamDelete( demux_t *p_demux, logical_stream_t *p_strea
     es_format_Clean( &p_stream->fmt_old );
     es_format_Clean( &p_stream->fmt );
 
+    if ( p_stream->idx != NULL)
+    {
+        oggseek_index_entries_free( p_stream->idx );
+    }
+
     free( p_stream );
 }
 /**
index 3139abb1badf2c3ee1c0c39bc9f2401d9ee48cee..60fbfce06630b7d2658cdbea296fae2912918cbd 100644 (file)
@@ -69,6 +69,15 @@ typedef struct logical_stream_s
     /* offset of first keyframe for theora; can be 0 or 1 depending on version number */
     int64_t i_keyframe_offset;
 
+    /* keyframe index for seeking, created as we discover keyframes */
+    demux_index_entry_t *idx;
+
+    /* skip some frames after a seek */
+    int i_skip_frames;
+
+    /* data start offset (absolute) in bytes */
+    int64_t i_data_start;
+
     /* kate streams have the number of headers in the ID header */
     int i_kate_num_headers;
 
@@ -105,6 +114,18 @@ struct demux_sys_t
     /* after reading all headers, the first data page is stuffed into the relevant stream, ready to use */
     bool    b_page_waiting;
 
+    /* count of total frames in video stream */
+    int64_t i_total_frames;
+
+    /* length of file in bytes */
+    int64_t i_total_length;
+
+    /* offset position in file (for reading) */
+    int64_t i_input_position;
+
+    /* current page being parsed */
+    ogg_page current_page;
+
     mtime_t i_st_pts;
 
 
diff --git a/modules/demux/oggseek.c b/modules/demux/oggseek.c
new file mode 100644 (file)
index 0000000..0f72f5b
--- /dev/null
@@ -0,0 +1,381 @@
+/*****************************************************************************
+ * oggseek.c : ogg seeking functions for ogg demuxer vlc
+ *****************************************************************************
+ * Copyright (C) 2008 - 2010 Gabriel Finch <salsaman@gmail.com>
+ *
+ * Authors: Gabriel Finch <salsaman@gmail.com>
+ * adapted from: http://lives.svn.sourceforge.net/viewvc/lives/trunk/lives-plugins
+ * /plugins/decoders/ogg_theora_decoder.c
+ *
+ * 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
+ * (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.
+ *
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_demux.h>
+
+#include <ogg/ogg.h>
+
+#include "ogg.h"
+#include "oggseek.h"
+
+
+/************************************************************
+* index entries
+*************************************************************/
+
+/* free all entries in index list */
+
+void oggseek_index_entries_free ( demux_index_entry_t *idx )
+{
+    demux_index_entry_t *idx_next;
+
+    while ( idx != NULL )
+    {
+        idx_next = idx->p_next;
+        free( idx );
+        idx = idx_next;
+    }
+}
+
+
+/* unlink and free idx. If idx is head of list, return new head */
+
+static demux_index_entry_t *index_entry_delete( demux_index_entry_t *idx )
+{
+    demux_index_entry_t *xidx = idx;
+
+    if ( idx->p_prev != NULL ) idx->p_prev->p_next = idx->p_next;
+    else xidx = idx->p_next;
+
+    if ( idx->p_next != NULL ) idx->p_next->p_prev = idx->p_prev;
+    free( idx );
+
+    return xidx;
+}
+
+
+/* internal function to create a new list member */
+
+static demux_index_entry_t *index_entry_new( void )
+{
+    demux_index_entry_t *idx = (demux_index_entry_t *)malloc( sizeof( demux_index_entry_t ) );
+    idx->p_next = idx->p_prev = NULL;
+    idx->i_pagepos_end = -1;
+    return idx;
+}
+
+
+
+
+/*********************************************************************
+ * private functions
+ **********************************************************************/
+
+/* seek in ogg file to offset i_pos and update the sync */
+
+static void seek_byte( demux_t *p_demux, int64_t i_pos )
+{
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    if ( ! stream_Seek( p_demux->s, i_pos ) )
+    {
+        ogg_sync_reset( &p_sys->oy );
+
+        p_sys->i_input_position = i_pos;
+        p_sys->b_page_waiting = false;
+    }
+}
+
+
+
+/* read bytes from the ogg file to try to find a page start */
+
+static int64_t get_data( demux_t *p_demux, int64_t i_bytes_to_read )
+{
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    char *buf;
+    int64_t i_result;
+
+    if ( p_sys->i_total_length > 0 )
+    {
+        if ( p_sys->i_input_position + i_bytes_to_read > p_sys->i_total_length )
+        {
+            i_bytes_to_read = p_sys->i_total_length - p_sys->i_input_position;
+            if ( i_bytes_to_read <= 0 ) {
+                return 0;
+            }
+        }
+    }
+
+    seek_byte ( p_demux, p_sys->i_input_position );
+
+    buf = ogg_sync_buffer( &p_sys->oy, i_bytes_to_read );
+
+    i_result = stream_Read( p_demux->s, buf, i_bytes_to_read );
+
+    p_sys->b_page_waiting = false;
+
+    ogg_sync_wrote( &p_sys->oy, i_result );
+    return i_result;
+}
+
+
+
+
+
+
+/* Find the last frame for p_stream,
+   -1 is returned on failure */
+
+static int64_t find_last_frame (demux_t *p_demux, logical_stream_t *p_stream)
+{
+
+    return -1;
+}
+
+
+
+
+/* seek to a suitable point to begin decoding for i_tframe. We can pre-set bounding positions 
+   i_pos_lower and i_pos_higher to narrow the search domain. */
+
+
+static int64_t ogg_seek( demux_t *p_demux, logical_stream_t *p_stream, int64_t i_tframe, 
+                         int64_t i_pos_lower, int64_t i_pos_upper, int64_t *pi_pagepos, 
+                         bool b_exact )
+{
+    /* For theora:
+     * We do two passes here, first with b_exact set, then with b_exact unset.
+     *
+     * If b_exact is set, we find the highest granulepos <= the target granulepos
+     * from this we extract an estimate of the keyframe (note that there could be other 
+     * "hidden" keyframes between the found granulepos and the target).
+     *
+     * On the second pass we find the highest granulepos < target. This places us just before or 
+     * at the start of the target keyframe.
+     *
+     * When we come to decode, we start from this second position, discarding any completed 
+     * packets on that page, and read pages discarding packets until we get to the target frame.
+     *
+     * The function returns the granulepos which is found, 
+     * sets the page offset in pi_pagepos. -1 is returned on error.
+     *
+     * for dirac:
+     *
+     * we find the highest sync frame <= target frame, and return the sync_frame number
+     * b_exact should be set to true
+     *
+     *
+     * the method used is bi-sections:
+     *  - we check the lower keyframe
+     * if this is == target we return
+     * if > target, or we find no keyframes, we go to the lower segment
+     * if < target we divide the segment in two and check the upper half
+     *
+     * This is then repeated until the segment size is too small to hold a packet, 
+     * at which point we return our best match
+     *
+     * Two optimisations are made: - anything we discover about keyframes is added to our index
+     * - before calling this function we get approximate bounds from the index
+     *
+     * therefore, subsequent searches become more rapid.
+     *
+     */
+
+    return -1;
+}
+
+
+
+
+
+
+/* find upper and lower pagepos for i_tframe; if we find an exact match, we return it */
+
+static demux_index_entry_t *get_bounds_for ( logical_stream_t *p_stream, int64_t i_tframe, 
+                                             int64_t *pi_pos_lower, int64_t *pi_pos_upper)
+{
+
+    return NULL;
+}
+
+
+/************************************************************************
+ * public functions
+ *************************************************************************/
+
+
+
+/* return highest frame number for p_stream (which must be a theora or dirac video stream) */
+
+int64_t oggseek_get_last_frame ( demux_t *p_demux, logical_stream_t *p_stream )
+{
+
+    /* unhandled video format */
+    return -1;
+}
+
+
+
+
+
+
+/* seek to target frame in p_stream; actually we will probably end up just before it
+ *   (so we set skip)
+ *
+ * range for i_tframe is 0 -> p_sys->i_total_frames - 1
+ */
+
+int oggseek_find_frame ( demux_t *p_demux, logical_stream_t *p_stream, int64_t i_tframe )
+{
+
+    const demux_index_entry_t *fidx;
+
+    /* lower and upper bounds for search domain */
+    int64_t i_pos_lower;
+    int64_t i_pos_upper;
+
+    int64_t i_granulepos;
+    int64_t i_pagepos;
+
+    /* keyframe for i_tframe ( <= i_tframe ) */
+    int64_t i_kframe;
+
+    /* keyframe for i_kframe ( <= i_kframe ) */
+    int64_t i_xkframe;
+
+    /* next frame to be decoded ( >= i_xkframe ) */
+    int64_t i_cframe;
+
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    i_tframe += p_stream->i_keyframe_offset;
+
+    /* reduce the search domain */
+    fidx = get_bounds_for( p_stream, i_tframe, &i_pos_lower, &i_pos_upper );
+
+    if ( fidx == NULL )
+    {
+        /* no exact match found; search the domain for highest keyframe <= i_tframe */
+
+        i_granulepos = ogg_seek ( p_demux, p_stream, i_tframe, i_pos_lower, i_pos_upper, 
+                                  &i_pagepos, true );
+        if ( i_granulepos == -1 )
+        {
+            return VLC_EGENERIC;
+        }
+
+    }
+    else {
+        i_granulepos = fidx->i_value;
+    }
+
+    return VLC_EGENERIC;
+
+}
+
+
+
+
+
+
+/****************************************************************************
+ * oggseek_read_page: Read a full Ogg page from the physical bitstream.
+ ****************************************************************************
+ * Returns number of bytes read. This should always be > 0
+ * unless we are at the end of stream.
+ *
+ ****************************************************************************/
+int64_t oggseek_read_page( demux_t *p_demux )
+{
+    demux_sys_t *p_ogg = p_demux->p_sys  ;
+    uint8_t header[PAGE_HEADER_BYTES+255];
+    int i_nsegs;
+    int i_in_pos;
+    int i;
+    int64_t i_result;
+    int i_page_size;
+    char *buf;
+
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    /* store position of this page */
+    i_in_pos = p_ogg->i_input_position = stream_Tell( p_demux->s );
+
+    if ( p_sys->b_page_waiting) {
+        msg_Warn( p_demux, "Ogg page already loaded" );
+        return 0;
+    }
+
+    if ( stream_Read ( p_demux->s, header, PAGE_HEADER_BYTES ) < PAGE_HEADER_BYTES )
+    {
+        stream_Seek( p_demux->s, i_in_pos );
+        msg_Dbg ( p_demux, "Reached clean EOF in ogg file" );
+        return 0;
+    }
+
+    i_nsegs = header[ PAGE_HEADER_BYTES - 1 ];
+
+    if ( stream_Read ( p_demux->s, header+PAGE_HEADER_BYTES, i_nsegs ) < i_nsegs )
+    {
+        stream_Seek( p_demux->s, i_in_pos );
+        msg_Warn ( p_demux, "Reached broken EOF in ogg file" );
+        return 0;
+    }
+
+    i_page_size = PAGE_HEADER_BYTES + i_nsegs;
+
+    for ( i = 0; i < i_nsegs; i++ )
+    {
+        i_page_size += header[ PAGE_HEADER_BYTES + i ];
+    }
+
+    ogg_sync_reset( &p_ogg->oy );
+
+    buf = ogg_sync_buffer( &p_ogg->oy, i_page_size );
+
+    memcpy( buf, header, PAGE_HEADER_BYTES + i_nsegs );
+
+    i_result = stream_Read ( p_demux->s, (uint8_t*)buf + PAGE_HEADER_BYTES + i_nsegs, 
+                             i_page_size - PAGE_HEADER_BYTES - i_nsegs );
+
+    ogg_sync_wrote( &p_ogg->oy, i_result + PAGE_HEADER_BYTES + i_nsegs );
+
+
+
+
+    if ( ogg_sync_pageout( &p_ogg->oy, &p_ogg->current_page ) != 1 )
+    {
+        msg_Err( p_demux , "Got invalid packet, read %"PRId64" of %i: %s",i_result,i_page_size,
+                 buf );
+        return 0;
+    }
+
+    p_sys->b_page_waiting = false;
+
+    return i_result + PAGE_HEADER_BYTES + i_nsegs;
+}
+
+
diff --git a/modules/demux/oggseek.h b/modules/demux/oggseek.h
new file mode 100644 (file)
index 0000000..b9a4348
--- /dev/null
@@ -0,0 +1,60 @@
+/*****************************************************************************
+ * oggseek.h : ogg seeking functions for ogg demuxer vlc
+ *****************************************************************************
+ * Copyright (C) 2008 - 2010 Gabriel Finch <salsaman@gmail.com>
+ *
+ * Authors: Gabriel Finch <salsaman@gmail.com>
+ * adapted from: http://lives.svn.sourceforge.net/viewvc/lives/trunk/lives-plugins
+ * /plugins/decoders/ogg_theora_decoder.c
+ *
+ * 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
+ * (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.
+ *
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#define PAGE_HEADER_BYTES 27
+
+#define OGGSEEK_BYTES_TO_READ 8500
+
+/* index entries are structured as follows:
+ *   - for theora, highest granulepos -> pagepos (bytes) where keyframe begins
+ *  - for dirac, kframe (sync point) -> pagepos of sequence start (?)
+ */
+
+/* this is typedefed to demux_index_entry_t in ogg.h */
+struct oggseek_index_entry
+{
+    demux_index_entry_t *p_next;
+    demux_index_entry_t *p_prev;
+
+    /* value is highest granulepos for theora, sync frame for dirac */
+    int64_t i_value;
+    int64_t i_pagepos;
+
+    /* not used for theora because the granulepos tells us this */
+    int64_t i_pagepos_end;
+};
+
+
+
+
+void oggseek_index_entries_free ( demux_index_entry_t * );
+
+int64_t oggseek_get_last_frame ( demux_t *, logical_stream_t *);
+
+int oggseek_find_frame ( demux_t *, logical_stream_t *, int64_t i_tframe );
+