]> git.sesse.net Git - vlc/blobdiff - modules/demux/oggseek.c
New sub-filter support add new "sub filter" capability for processing subpictures...
[vlc] / modules / demux / oggseek.c
index 0f72f5b02fcbd4a822381524e518e4bd5420d9e9..abe2a6be9d25ce398fbb0535c223932a2d948e3b 100644 (file)
@@ -86,6 +86,96 @@ static demux_index_entry_t *index_entry_new( void )
 
 
 
+/* add a theora entry to our list; format is highest granulepos -> page offset of 
+   keyframe start */
+
+const demux_index_entry_t *oggseek_theora_index_entry_add ( logical_stream_t *p_stream, 
+                                                            int64_t i_granule, 
+                                                            int64_t i_pagepos)
+{
+    /* add or update entry for keyframe */
+    demux_index_entry_t *idx;
+    demux_index_entry_t *oidx;
+    demux_index_entry_t *last_idx = NULL;
+    int64_t i_gpos;
+    int64_t i_frame;
+    int64_t i_kframe;
+    int64_t i_tframe;
+    int64_t i_tkframe;
+
+    if ( p_stream == NULL ) return NULL;
+
+    oidx = idx = p_stream->idx;
+
+    i_tkframe = i_granule >> p_stream->i_granule_shift;
+    i_tframe = i_tkframe + i_granule - ( i_tkframe << p_stream->i_granule_shift );
+
+    if ( i_tkframe < 1 ) return NULL;
+
+    if ( idx == NULL )
+    {
+        demux_index_entry_t *ie = index_entry_new();
+        ie->i_value = i_granule;
+        ie->i_pagepos = i_pagepos;
+        p_stream->idx = ie;
+        return ie;
+    }
+
+
+    while ( idx != NULL )
+    {
+        i_gpos = idx->i_value;
+
+        i_kframe = i_gpos >> p_stream->i_granule_shift;
+        if ( i_kframe > i_tframe ) break;
+
+        if ( i_kframe == i_tkframe )
+        {
+            /* entry exists, update it if applicable, and return it */
+            i_frame = i_kframe + i_gpos - ( i_kframe << p_stream->i_granule_shift );
+            if ( i_frame < i_tframe )
+            {
+                idx->i_value = i_granule;
+                idx->i_pagepos = i_pagepos;
+            }
+
+            return idx;
+        }
+
+        last_idx = idx;
+        idx = idx->p_next;
+    }
+
+
+    /* new entry; insert after last_idx */
+
+    idx = index_entry_new();
+
+    if ( last_idx != NULL )
+    {
+        idx->p_next = last_idx->p_next;
+        last_idx->p_next = idx;
+        idx->p_prev = last_idx;
+    }
+    else
+    {
+        idx->p_next = oidx;
+        oidx = idx;
+    }
+
+    if ( idx->p_next != NULL )
+    {
+        idx->p_next->p_prev = idx;
+    }
+
+    idx->i_value = i_granule;
+    idx->i_pagepos = i_pagepos;
+
+    return idx;
+}
+
+
+
 
 /*********************************************************************
  * private functions
@@ -144,6 +234,149 @@ static int64_t get_data( demux_t *p_demux, int64_t i_bytes_to_read )
 
 
 
+/* Find the first first ogg page for p_stream between offsets i_pos1 and i_pos2,
+   return file offset in bytes; -1 is returned on failure */
+
+static int64_t find_first_page( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2, 
+                                logical_stream_t *p_stream, 
+                                int64_t *pi_kframe, int64_t *pi_frame )
+{
+    int64_t i_result;
+    int64_t i_granulepos;
+    int64_t i_bytes_to_read = i_pos2 - i_pos1 + 1;
+    int64_t i_bytes_read;
+    int64_t i_pages_checked = 0;
+    int64_t i_packets_checked;
+
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    ogg_packet op;
+
+    seek_byte( p_demux, i_pos1 );
+
+    if ( i_pos1 == p_stream->i_data_start )
+    {
+        /* set a dummy granulepos at data_start */
+        *pi_kframe = p_stream->i_keyframe_offset;
+        *pi_frame = p_stream->i_keyframe_offset;
+
+        p_sys->b_page_waiting = true;
+        return p_sys->i_input_position;
+    }
+
+    if ( i_bytes_to_read > OGGSEEK_BYTES_TO_READ ) i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
+
+    while ( 1 )
+    {
+
+        if ( p_sys->i_input_position >= i_pos2 )
+        {
+            /* we reached the end and found no pages */
+            *pi_frame=-1;
+            return -1;
+        }
+
+        /* read next chunk */
+        if ( ! ( i_bytes_read = get_data( p_demux, i_bytes_to_read ) ) )
+        {
+            /* EOF */
+            *pi_frame = -1;
+            return -1;
+        }
+
+        i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
+
+        i_result = ogg_sync_pageseek( &p_sys->oy, &p_sys->current_page );
+
+        if ( i_result < 0 )
+        {
+            /* found a page, sync to page start */
+            p_sys->i_input_position -= i_result;
+            i_pos1 = p_sys->i_input_position;
+            continue;
+        }
+
+        if ( i_result > 0 || ( i_result == 0 && p_sys->oy.fill > 3 && 
+                               ! strncmp( (char *)p_sys->oy.data, "OggS" , 4 ) ) )
+        {
+            i_pos1 = p_sys->i_input_position;
+            break;
+        }
+
+        p_sys->i_input_position += i_bytes_read;
+
+    };
+
+    seek_byte( p_demux, p_sys->i_input_position );
+    ogg_stream_reset( &p_stream->os );
+
+    while( 1 )
+    {
+
+        if ( p_sys->i_input_position >= i_pos2 )
+        {
+            /* reached the end of the search region and nothing was found */
+            *pi_frame = -1;
+            return p_sys->i_input_position;
+        }
+
+        p_sys->b_page_waiting = false;
+
+        if ( ! ( i_result = oggseek_read_page( p_demux ) ) )
+        {
+            /* EOF */
+            *pi_frame = -1;
+            return p_sys->i_input_position;
+        }
+
+        // found a page
+        if ( p_stream->os.serialno != ogg_page_serialno( &p_sys->current_page ) )
+        {
+            /* page is not for this stream */
+            p_sys->i_input_position += i_result;
+            if ( ! i_pages_checked ) i_pos1 = p_sys->i_input_position;
+            continue;
+        }
+
+
+        ogg_stream_pagein( &p_stream->os, &p_sys->current_page );
+
+        i_pages_checked++;
+        i_packets_checked = 0;
+
+        if ( ogg_stream_packetout( &p_stream->os, &op ) > 0 )
+        {
+            i_packets_checked++;
+        }
+
+        if ( i_packets_checked )
+        {
+            i_granulepos = ogg_page_granulepos( &p_sys->current_page );
+
+            oggseek_theora_index_entry_add( p_stream, i_granulepos, i_pos1 );
+
+            *pi_kframe =
+                i_granulepos >> p_stream->i_granule_shift;
+
+            *pi_frame = *pi_kframe +
+                i_granulepos - ( *pi_kframe << p_stream->i_granule_shift );
+
+            p_sys->b_page_waiting = true;
+            return i_pos1;
+
+        }
+
+        /*  -> start of next page */
+        p_sys->i_input_position += i_result;
+    }
+}
+
+
+
+
+
+
+
 
 /* Find the last frame for p_stream,
    -1 is returned on failure */
@@ -151,12 +384,81 @@ static int64_t get_data( demux_t *p_demux, int64_t i_bytes_to_read )
 static int64_t find_last_frame (demux_t *p_demux, logical_stream_t *p_stream)
 {
 
+    int64_t i_page_pos;
+    int64_t i_start_pos;
+    int64_t i_frame = -1;
+    int64_t i_last_frame = -1;
+    int64_t i_kframe = 0;
+    int64_t i_pos1;
+    int64_t i_pos2;
+    int64_t i_serialno;
+
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    i_pos1 = p_stream->i_data_start;
+    i_pos2 = p_sys->i_total_length;
+    i_serialno = p_stream->os.serialno;
+
+    i_start_pos = i_pos2 - OGGSEEK_BYTES_TO_READ;
+
+
+    while( 1 )
+    {
+        if ( i_start_pos < i_pos1 ) i_start_pos = i_pos1;
+
+        i_page_pos = find_first_page( p_demux, i_start_pos, i_pos2, p_stream, &i_kframe, &i_frame );
+
+        if ( i_frame == -1 )
+        {
+            /* no pages found in range */
+            if ( i_last_frame >= 0 )
+            {
+                /* No more pages in range -> return last one */
+                return i_last_frame;
+            }
+            if ( i_start_pos <= i_pos1 )
+            {
+                return -1;
+            }
+
+            /* Go back a bit */
+            i_pos2 -= i_start_pos;
+            i_start_pos -= OGGSEEK_BYTES_TO_READ;
+            if ( i_start_pos < i_pos1 ) i_start_pos = i_pos1;
+            i_pos2 += i_start_pos;
+        }
+        else
+        {
+            /* found a page, see if we can find another one */
+            i_last_frame = i_frame;
+            i_start_pos = i_page_pos + 1;
+        }
+    }
     return -1;
 }
 
 
 
 
+
+
+/* convert a theora frame to a granulepos */
+
+static inline int64_t frame_to_gpos( logical_stream_t *p_stream, int64_t i_kframe, 
+                                     int64_t i_frame )
+{
+    if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
+    {
+        return ( i_kframe << p_stream->i_granule_shift ) + ( i_frame - i_kframe );
+    }
+
+    return i_kframe;
+}
+
+
+
+
+
 /* 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. */
 
@@ -203,7 +505,131 @@ static int64_t ogg_seek( demux_t *p_demux, logical_stream_t *p_stream, int64_t i
      *
      */
 
+    int64_t i_start_pos;
+    int64_t i_end_pos;
+    int64_t i_pagepos;
+    int64_t i_segsize;
+    int64_t i_frame;
+    int64_t i_kframe;
+
+    int64_t i_best_kframe = -1;
+    int64_t i_best_frame = -1;
+    int64_t i_best_pagepos = -1;
+
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    if ( i_tframe < p_stream->i_keyframe_offset )
+    {
+        *pi_pagepos = p_stream->i_data_start;
+
+        if ( ! b_exact ) {
+            seek_byte( p_demux, p_stream->i_data_start );
+            return frame_to_gpos( p_stream, p_stream->i_keyframe_offset, 1 );
+        }
+        return frame_to_gpos( p_stream, p_stream->i_keyframe_offset, 0 );
+    }
+
+    if ( i_pos_lower < p_stream->i_data_start )
+    {
+        i_pos_lower = p_stream->i_data_start;
+    }
+
+    if ( i_pos_upper < 0 )
+    {
+        i_pos_upper = p_sys->i_total_length;
+    }
+
+    if ( i_pos_upper > p_sys->i_total_length )
+    {
+        i_pos_upper = p_sys->i_total_length;
+    }
+
+    i_start_pos = i_pos_lower;
+    i_end_pos = i_pos_upper;
+
+    i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
+
+    do
+    {
+        /* see if the frame lies in current segment */
+        if ( i_start_pos < i_pos_lower )
+        {
+            i_start_pos = i_pos_lower;
+        }
+        if ( i_end_pos > i_pos_upper )
+        {
+            i_end_pos = i_pos_upper;
+        }
+
+        if ( i_start_pos >= i_end_pos )
+        {
+            if ( i_start_pos == i_pos_lower)
+            {
+                if ( ! b_exact ) seek_byte( p_demux, i_start_pos );
+                *pi_pagepos = i_start_pos;
+                return frame_to_gpos( p_stream, p_stream->i_keyframe_offset, 1 );
+            }
+            break;
+        }
+
+        if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
+        {
+            i_pagepos = find_first_page( p_demux, i_start_pos, i_end_pos, p_stream, 
+                                         &i_kframe, &i_frame );
+        }
+        else return -1;
+
+        if ( i_pagepos != -1 && i_kframe != -1 )
+        {
+            /* found a page */
+
+            if ( b_exact && i_frame >= i_tframe && i_kframe <= i_tframe )
+            {
+                /* got it ! */
+                *pi_pagepos = i_start_pos;
+                return frame_to_gpos( p_stream, i_kframe, i_frame );
+            }
+
+            if ( ( i_kframe < i_tframe || ( b_exact && i_kframe == i_tframe ) ) 
+                 && i_kframe > i_best_kframe )
+            {
+                i_best_kframe = i_kframe;
+                i_best_frame = i_frame;
+                i_best_pagepos = i_pagepos;
+            }
+
+            if ( i_frame >= i_tframe )
+            {
+                /* check lower half of segment */
+                i_start_pos -= i_segsize;
+                i_end_pos -= i_segsize;
+            }
+
+            else i_start_pos = i_pagepos;
+
+        }
+        else
+        {
+            /* no keyframe found, check lower segment */
+            i_end_pos -= i_segsize;
+            i_start_pos -= i_segsize;
+        }
+
+        i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
+        i_start_pos += i_segsize;
+
+    } while ( i_segsize > 64 );
+
+    if ( i_best_kframe >- 1 )
+    {
+        if ( !b_exact )
+        {
+            seek_byte( p_demux, i_best_pagepos );
+        }
+        *pi_pagepos = i_best_pagepos;
+        return frame_to_gpos( p_stream, i_best_kframe, i_best_frame );
+    }
+
     return -1;
 }
 
@@ -217,21 +643,94 @@ static int64_t ogg_seek( demux_t *p_demux, logical_stream_t *p_stream, int64_t i
 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)
 {
+    int64_t i_kframe;
+    int64_t i_frame;
+    int64_t i_gpos;
+
+    demux_index_entry_t *idx = p_stream->idx;
+
+    *pi_pos_lower = *pi_pos_upper = -1;
+
+    while ( idx != NULL )
+    {
+
+        if ( idx-> i_pagepos < 0 )
+        {
+            /* kframe was found to be invalid */
+            idx = idx->p_next;
+            continue;
+        }
+
+        if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
+        {
+            i_gpos = idx->i_value;
+            i_kframe = i_gpos >> p_stream->i_granule_shift;
+            i_frame = i_kframe + i_gpos - ( i_kframe << p_stream->i_granule_shift );
+        }
+        else return NULL;
+
+
+        if ( i_kframe > i_tframe )
+        {
+            *pi_pos_upper = idx->i_pagepos;
+            return NULL;
+        }
+
+        if ( i_frame < i_tframe )
+        {
+            *pi_pos_lower = idx->i_pagepos;
+            idx = idx->p_next;
+            continue;
+        }
+
+        return idx;
+    }
 
     return NULL;
 }
 
 
+/* get highest frame in theora stream */
+
+static int64_t find_last_theora_frame ( demux_t *p_demux, logical_stream_t *p_stream )
+{
+    int64_t i_frame;
+
+    i_frame = find_last_frame ( p_demux, p_stream );
+
+    /* We need to reset back to the start here, otherwise packets cannot be decoded.
+     * I think this is due to the fact that we seek to the end and then we must reset 
+     * all logical streams, which causes remaining headers not to be read correctly.
+     * Seeking to 0 is the only value which seems to work, and it appears to have no
+     * adverse effects. */
+
+    seek_byte( p_demux, 0 );
+
+    return i_frame;
+}
+
+
+
 /************************************************************************
  * 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 )
 {
+    int64_t i_frame = -1;
+
+    if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
+    {
+        i_frame = find_last_theora_frame ( p_demux, p_stream );
+
+        if ( i_frame < 0 ) return -1;
+        return i_frame;
+    }
 
     /* unhandled video format */
     return -1;
@@ -292,8 +791,44 @@ int oggseek_find_frame ( demux_t *p_demux, logical_stream_t *p_stream, int64_t i
         i_granulepos = fidx->i_value;
     }
 
-    return VLC_EGENERIC;
+    if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
+    {
+        i_kframe = i_granulepos >> p_stream->i_granule_shift;
+        if ( i_kframe < p_stream->i_keyframe_offset )
+        {
+            i_kframe = p_stream->i_keyframe_offset;
+        }
+
+        /* we found a keyframe, but we don't know where its packet starts, so search for a 
+           frame just before it */
+
+        /* reduce search domain */
+        get_bounds_for( p_stream, i_kframe-1, &i_pos_lower, &i_pos_upper );
+
+        i_granulepos = ogg_seek( p_demux, p_stream, i_kframe-1, i_pos_lower, i_pos_upper, 
+                                 &i_pagepos, false );
+
+        /* i_cframe will be the next frame we decode */
+        i_xkframe = i_granulepos >> p_stream->i_granule_shift;
+        i_cframe = i_xkframe + i_granulepos - ( i_xkframe << p_stream->i_granule_shift) + 1;
+
+        if ( p_sys->i_input_position == p_stream->i_data_start )
+        {
+            i_cframe = i_kframe = p_stream->i_keyframe_offset;
+        }
+        else
+        {
+            oggseek_theora_index_entry_add( p_stream, i_granulepos, p_sys->i_input_position );
+        }
+
+    }
+    else return VLC_EGENERIC;
+
+    p_stream->i_skip_frames = i_tframe - i_cframe;
+
+    ogg_stream_reset( &p_stream->os );
 
+    return VLC_SUCCESS;
 }