1 /*****************************************************************************
2 * oggseek.c : ogg seeking functions for ogg demuxer vlc
3 *****************************************************************************
4 * Copyright (C) 2008 - 2010 Gabriel Finch <salsaman@gmail.com>
6 * Authors: Gabriel Finch <salsaman@gmail.com>
7 * adapted from: http://lives.svn.sourceforge.net/viewvc/lives/trunk/lives-plugins
8 * /plugins/decoders/ogg_theora_decoder.c
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_demux.h>
45 #define THEORA_FTYPE_NOTDATA 0x80
46 #define THEORA_FTYPE_INTERFRAME 0x40
48 #define SEGMENT_NOT_FOUND -1
50 #define MAX_PAGE_SIZE 65307
51 typedef struct packetStartCoordinates
56 } packetStartCoordinates;
58 //#define OGG_SEEK_DEBUG 1
60 #define OggDebug(code) code
61 #define OggNoDebug(code)
63 #define OggDebug(code)
64 #define OggNoDebug(code) code
66 /************************************************************
68 *************************************************************/
70 /* free all entries in index list */
72 void oggseek_index_entries_free ( demux_index_entry_t *idx )
74 demux_index_entry_t *idx_next;
78 idx_next = idx->p_next;
85 /* internal function to create a new list member */
87 static demux_index_entry_t *index_entry_new( void )
89 demux_index_entry_t *idx = xmalloc( sizeof( demux_index_entry_t ) );
90 if ( !idx ) return NULL;
91 idx->p_next = idx->p_prev = NULL;
92 idx->i_pagepos_end = -1;
98 /* add a theora entry to our list; format is highest granulepos -> page offset of
101 const demux_index_entry_t *oggseek_theora_index_entry_add ( logical_stream_t *p_stream,
105 /* add or update entry for keyframe */
106 demux_index_entry_t *idx;
107 demux_index_entry_t *oidx;
108 demux_index_entry_t *last_idx = NULL;
115 if ( p_stream == NULL ) return NULL;
117 oidx = idx = p_stream->idx;
119 i_tkframe = i_granule >> p_stream->i_granule_shift;
120 i_tframe = i_tkframe + i_granule - ( i_tkframe << p_stream->i_granule_shift );
122 if ( i_tkframe < 1 ) return NULL;
126 demux_index_entry_t *ie = index_entry_new();
127 ie->i_value = i_granule;
128 ie->i_pagepos = i_pagepos;
134 while ( idx != NULL )
136 i_gpos = idx->i_value;
138 i_kframe = i_gpos >> p_stream->i_granule_shift;
139 if ( i_kframe > i_tframe ) break;
141 if ( i_kframe == i_tkframe )
143 /* entry exists, update it if applicable, and return it */
144 i_frame = i_kframe + i_gpos - ( i_kframe << p_stream->i_granule_shift );
145 if ( i_frame < i_tframe )
147 idx->i_value = i_granule;
148 idx->i_pagepos = i_pagepos;
159 /* new entry; insert after last_idx */
161 idx = index_entry_new();
163 if ( last_idx != NULL )
165 idx->p_next = last_idx->p_next;
166 last_idx->p_next = idx;
167 idx->p_prev = last_idx;
175 if ( idx->p_next != NULL )
177 idx->p_next->p_prev = idx;
180 idx->i_value = i_granule;
181 idx->i_pagepos = i_pagepos;
186 /* We insert into index, sorting by pagepos (as a page can match multiple
188 const demux_index_entry_t *OggSeek_IndexAdd ( logical_stream_t *p_stream,
192 demux_index_entry_t *idx;
193 demux_index_entry_t *oidx;
194 demux_index_entry_t *last_idx = NULL;
196 if ( p_stream == NULL ) return NULL;
198 oidx = idx = p_stream->idx;
200 if ( i_timestamp < 1 || i_pagepos < 1 ) return NULL;
204 demux_index_entry_t *ie = index_entry_new();
205 if ( !ie ) return NULL;
206 ie->i_value = i_timestamp;
207 ie->i_pagepos = i_pagepos;
212 while ( idx != NULL )
214 if ( idx->i_pagepos > i_pagepos ) break;
219 /* new entry; insert after last_idx */
220 idx = index_entry_new();
221 if ( !idx ) return NULL;
222 if ( last_idx != NULL )
224 idx->p_next = last_idx->p_next;
225 last_idx->p_next = idx;
226 idx->p_prev = last_idx;
234 if ( idx->p_next != NULL )
236 idx->p_next->p_prev = idx;
239 idx->i_value = i_timestamp;
240 idx->i_pagepos = i_pagepos;
245 static bool OggSeekIndexFind ( logical_stream_t *p_stream, int64_t i_timestamp,
246 int64_t *pi_pos_lower, int64_t *pi_pos_upper )
248 demux_index_entry_t *idx = p_stream->idx;
250 while ( idx != NULL )
252 if ( idx->i_value <= i_timestamp )
254 if ( !idx->p_next ) /* found on last index */
256 *pi_pos_lower = idx->i_pagepos;
259 if ( idx->p_next->i_value > i_timestamp )
261 *pi_pos_lower = idx->i_pagepos;
262 *pi_pos_upper = idx->p_next->i_pagepos;
272 /*********************************************************************
274 **********************************************************************/
276 /* seek in ogg file to offset i_pos and update the sync */
278 static void seek_byte( demux_t *p_demux, int64_t i_pos )
280 demux_sys_t *p_sys = p_demux->p_sys;
282 if ( ! stream_Seek( p_demux->s, i_pos ) )
284 ogg_sync_reset( &p_sys->oy );
286 p_sys->i_input_position = i_pos;
287 p_sys->b_page_waiting = false;
293 /* read bytes from the ogg file to try to find a page start */
295 static int64_t get_data( demux_t *p_demux, int64_t i_bytes_to_read )
297 demux_sys_t *p_sys = p_demux->p_sys;
302 if ( p_sys->i_total_length > 0 )
304 if ( p_sys->i_input_position + i_bytes_to_read > p_sys->i_total_length )
306 i_bytes_to_read = p_sys->i_total_length - p_sys->i_input_position;
307 if ( i_bytes_to_read <= 0 ) {
313 i_bytes_to_read = __MIN( i_bytes_to_read, INT_MAX );
315 seek_byte ( p_demux, p_sys->i_input_position );
317 buf = ogg_sync_buffer( &p_sys->oy, i_bytes_to_read );
319 i_result = stream_Read( p_demux->s, buf, i_bytes_to_read );
321 p_sys->b_page_waiting = false;
323 ogg_sync_wrote( &p_sys->oy, i_result );
328 void Oggseek_ProbeEnd( demux_t *p_demux )
330 /* Temporary state */
334 demux_sys_t *p_sys = p_demux->p_sys;
335 int64_t i_pos, i_startpos, i_result, i_granule, i_lowerbound;
336 int64_t i_length = 0;
337 int64_t i_backup_pos = stream_Tell( p_demux->s );
338 int64_t i_upperbound = stream_Size( p_demux->s );
339 unsigned int i_backoffset = OGGSEEK_BYTES_TO_READ;
340 assert( OGGSEEK_BYTES_TO_READ < UINT_MAX );
344 ogg_stream_init( &os, -1 );
345 ogg_sync_init( &oy );
347 /* Try to lookup last granule from each logical stream */
348 i_lowerbound = stream_Size( p_demux->s ) - p_sys->i_streams * MAX_PAGE_SIZE * 2;
349 i_lowerbound = __MAX( 0, i_lowerbound );
351 i_pos = i_startpos = __MAX( i_lowerbound, i_upperbound - i_backoffset );
353 if ( stream_Seek( p_demux->s, i_pos ) )
355 ogg_sync_clear( &oy );
356 ogg_stream_clear( &os );
360 while( i_pos >= i_lowerbound )
363 while( i_pos < i_upperbound )
366 i_result = ogg_sync_pageseek( &oy, &page );
368 buffer = ogg_sync_buffer( &oy, OGGSEEK_BYTES_TO_READ );
369 if ( buffer == NULL ) goto clean;
370 i_result = stream_Read( p_demux->s, (void*) buffer, OGGSEEK_BYTES_TO_READ );
371 if ( i_result < 1 ) goto clean;
373 ogg_sync_wrote( &oy, i_result );
375 while( ogg_sync_pageout( &oy, &page ) == 1 )
377 i_granule = ogg_page_granulepos( &page );
378 if ( i_granule == -1 ) continue;
380 for ( int i=0; i< p_sys->i_streams; i++ )
382 if ( p_sys->pp_stream[i]->i_serial_no != ogg_page_serialno( &page ) )
385 i_length = Oggseek_GranuleToAbsTimestamp( p_sys->pp_stream[i], i_granule, false );
386 p_sys->i_length = __MAX( p_sys->i_length, i_length / 1000000 );
390 if ( i_length > 0 ) break;
393 /* We found at least a page with valid granule */
394 if ( i_length > 0 ) break;
396 /* Otherwise increase read size, starting earlier */
397 if ( i_backoffset <= ( UINT_MAX >> 1 ) )
400 i_startpos = i_upperbound - i_backoffset;
404 i_startpos -= i_backoffset;
408 if ( stream_Seek( p_demux->s, i_pos ) )
413 stream_Seek( p_demux->s, i_backup_pos );
415 ogg_sync_clear( &oy );
416 ogg_stream_clear( &os );
419 /* convert a theora frame to a granulepos */
421 static inline int64_t frame_to_gpos( logical_stream_t *p_stream, int64_t i_kframe,
424 if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
426 return ( i_kframe << p_stream->i_granule_shift ) + ( i_frame - i_kframe );
433 static int64_t find_first_page_granule( demux_t *p_demux,
434 int64_t i_pos1, int64_t i_pos2,
435 logical_stream_t *p_stream,
436 int64_t *i_granulepos )
440 int64_t i_bytes_to_read = i_pos2 - i_pos1 + 1;
441 int64_t i_bytes_read;
442 int64_t i_pages_checked = 0;
443 int64_t i_packets_checked;
445 demux_sys_t *p_sys = p_demux->p_sys;
449 seek_byte( p_demux, i_pos1 );
451 if ( i_pos1 == p_stream->i_data_start )
453 p_sys->b_page_waiting = true;
454 return p_sys->i_input_position;
457 if ( i_bytes_to_read > OGGSEEK_BYTES_TO_READ ) i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
462 if ( p_sys->i_input_position >= i_pos2 )
464 /* we reached the end and found no pages */
468 /* read next chunk */
469 if ( ! ( i_bytes_read = get_data( p_demux, i_bytes_to_read ) ) )
475 i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
477 i_result = ogg_sync_pageseek( &p_sys->oy, &p_sys->current_page );
481 /* found a page, sync to page start */
482 p_sys->i_input_position -= i_result;
483 i_pos1 = p_sys->i_input_position;
487 if ( i_result > 0 || ( i_result == 0 && p_sys->oy.fill > 3 &&
488 ! strncmp( (char *)p_sys->oy.data, "OggS" , 4 ) ) )
490 i_pos1 = p_sys->i_input_position;
494 p_sys->i_input_position += i_bytes_read;
498 seek_byte( p_demux, p_sys->i_input_position );
499 ogg_stream_reset( &p_stream->os );
504 if ( p_sys->i_input_position >= i_pos2 )
506 /* reached the end of the search region and nothing was found */
507 return p_sys->i_input_position;
510 p_sys->b_page_waiting = false;
512 if ( ! ( i_result = oggseek_read_page( p_demux ) ) )
515 return p_sys->i_input_position;
519 if ( p_stream->os.serialno != ogg_page_serialno( &p_sys->current_page ) )
521 /* page is not for this stream */
522 p_sys->i_input_position += i_result;
523 if ( ! i_pages_checked ) i_pos1 = p_sys->i_input_position;
527 ogg_stream_pagein( &p_stream->os, &p_sys->current_page );
530 i_packets_checked = 0;
532 if ( ogg_stream_packetout( &p_stream->os, &op ) > 0 )
537 if ( i_packets_checked )
539 *i_granulepos = ogg_page_granulepos( &p_sys->current_page );
540 p_sys->b_page_waiting = true;
545 /* -> start of next page */
546 p_sys->i_input_position += i_result;
550 /* Checks if current packet matches codec keyframe */
551 bool Ogg_IsKeyFrame( logical_stream_t *p_stream, ogg_packet *p_packet )
553 if ( p_stream->b_oggds )
555 return ( p_packet->bytes > 0 && p_packet->packet[0] & PACKET_IS_SYNCPOINT );
557 else if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
559 if ( p_packet->bytes <= 0 || p_packet->packet[0] & THEORA_FTYPE_NOTDATA )
562 return !( p_packet->packet[0] & THEORA_FTYPE_INTERFRAME );
564 else if ( p_stream->fmt.i_codec == VLC_CODEC_VP8 )
566 return ( ( ( p_packet->granulepos >> 3 ) & 0x07FFFFFF ) == 0 );
571 int64_t Ogg_GetKeyframeGranule( logical_stream_t *p_stream, int64_t i_granule )
573 if ( p_stream->b_oggds )
575 return -1; /* We have no way to know */
577 else if( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
579 return ( i_granule >> p_stream->i_granule_shift ) << p_stream->i_granule_shift;
581 else if( p_stream->fmt.i_codec == VLC_CODEC_DIRAC )
583 return ( i_granule >> 31 ) << 31;
585 /* No change, that's keyframe or it can't be shifted out (oggds) */
589 static bool OggSeekToPacket( demux_t *p_demux, logical_stream_t *p_stream,
590 int64_t i_granulepos, packetStartCoordinates *p_lastpacketcoords,
594 demux_sys_t *p_sys = p_demux->p_sys;
595 ogg_stream_pagein( &p_stream->os, &p_sys->current_page );
598 int64_t itarget_frame = Ogg_GetKeyframeGranule( p_stream, i_granulepos );
599 int64_t iframe = Ogg_GetKeyframeGranule( p_stream, ogg_page_granulepos( &p_sys->current_page ) );
601 if ( ! ogg_page_continued( &p_sys->current_page ) )
603 /* Start of frame, not continued page, but no packet. */
604 p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
605 p_lastpacketcoords->i_pos = p_sys->i_input_position;
606 p_lastpacketcoords->i_skip = 0;
609 if ( b_exact && iframe > itarget_frame )
611 while( ogg_stream_packetout( &p_stream->os, &op ) > 0 ) {};
615 while( ogg_stream_packetpeek( &p_stream->os, &op ) > 0 )
617 if ( ( !b_exact || itarget_frame == iframe ) && Ogg_IsKeyFrame( p_stream, &op ) )
620 msg_Dbg(p_demux, "** KEYFRAME **" );
621 msg_Dbg(p_demux, "** KEYFRAME PACKET START pageno %"PRId64" OFFSET %"PRId64" skip %"PRId64" **", p_lastpacketcoords->i_pageno, p_lastpacketcoords->i_pos, p_lastpacketcoords->i_skip );
622 msg_Dbg(p_demux, "KEYFRAME PACKET IS at pageno %"PRId64" OFFSET %"PRId64" with skip %d packet (%d / %d) ",
623 ogg_page_pageno( &p_sys->current_page ), p_sys->i_input_position, i, i+1, ogg_page_packets( &p_sys->current_page ) );
624 DemuxDebug( p_sys->b_seeked = true; )
627 if ( i != 0 ) /* Not continued packet */
629 /* We need to handle the case when the packet spans onto N
630 previous page(s). packetout() will be valid only when
631 all segments are assembled.
632 Keyframe flag is only available after assembling last part
633 (when packetout() becomes valid). We have no way to guess
634 keyframe at earlier time.
636 p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
637 p_lastpacketcoords->i_pos = p_sys->i_input_position;
638 p_lastpacketcoords->i_skip = i;
644 p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
645 p_lastpacketcoords->i_pos = p_sys->i_input_position;
646 p_lastpacketcoords->i_skip = i + 1;
648 /* remove that packet and go sync to next */
649 ogg_stream_packetout( &p_stream->os, &op );
654 static int64_t OggForwardSeekToFrame( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2,
655 logical_stream_t *p_stream, int64_t i_granulepos, bool b_fastseek )
658 int64_t i_bytes_to_read;
659 int64_t i_bytes_read;
661 demux_sys_t *p_sys = p_demux->p_sys;
663 i_bytes_to_read = i_pos2 - i_pos1 + 1;
664 seek_byte( p_demux, i_pos1 );
665 if ( i_bytes_to_read > OGGSEEK_BYTES_TO_READ ) i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
668 msg_Dbg( p_demux, "Probing Fwd %"PRId64" %"PRId64" for granule %"PRId64,
669 i_pos1, i_pos2, i_granulepos );
675 if ( p_sys->i_input_position >= i_pos2 )
676 return SEGMENT_NOT_FOUND;
678 /* read next chunk */
679 if ( ! ( i_bytes_read = get_data( p_demux, i_bytes_to_read ) ) )
680 return SEGMENT_NOT_FOUND;
682 i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
684 i_result = ogg_sync_pageseek( &p_sys->oy, &p_sys->current_page );
688 /* found a page, sync to page start */
689 p_sys->i_input_position -= i_result;
690 i_pos1 = p_sys->i_input_position;
694 if ( i_result > 0 || ( i_result == 0 && p_sys->oy.fill > 3 &&
695 ! strncmp( (char *)p_sys->oy.data, "OggS" , 4 ) ) )
697 i_pos1 = p_sys->i_input_position;
701 p_sys->i_input_position += i_bytes_read;
704 seek_byte( p_demux, p_sys->i_input_position );
705 ogg_stream_reset( &p_stream->os );
707 packetStartCoordinates lastpacket = { -1, -1, -1 };
712 if ( p_sys->i_input_position >= i_pos2 )
714 /* reached the end of the search region and nothing was found */
718 p_sys->b_page_waiting = false;
720 if ( ! ( i_result = oggseek_read_page( p_demux ) ) )
727 if ( p_stream->os.serialno != ogg_page_serialno( &p_sys->current_page ) )
729 /* page is not for this stream */
730 p_sys->i_input_position += i_result;
734 if ( OggSeekToPacket( p_demux, p_stream, i_granulepos, &lastpacket, b_fastseek ) )
736 p_sys->i_input_position = lastpacket.i_pos;
737 p_stream->i_skip_frames = 0;
738 p_sys->b_page_waiting = true;
739 return p_sys->i_input_position;
742 /* -> start of next page */
743 p_sys->i_input_position += i_result;
746 return SEGMENT_NOT_FOUND;
749 static int64_t OggBackwardSeekToFrame( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2,
750 logical_stream_t *p_stream, int64_t i_granulepos )
753 int64_t i_offset = __MAX( 1 + ( (i_pos2 - i_pos1) >> 1 ), OGGSEEK_BYTES_TO_READ );
758 msg_Dbg( p_demux, "Probing Back %"PRId64" %"PRId64" for granule %"PRId64,
759 i_pos1, i_pos2, i_granulepos );
762 i_result = OggForwardSeekToFrame( p_demux, i_pos1, i_pos2, p_stream,
763 i_granulepos, true );
765 if ( i_result == SEGMENT_NOT_FOUND && i_pos1 > p_stream->i_data_start )
767 i_pos1 = __MAX( p_stream->i_data_start, i_pos1 - i_offset );
774 /* Dont use b_presentation with frames granules ! */
775 int64_t Oggseek_GranuleToAbsTimestamp( logical_stream_t *p_stream,
776 int64_t i_granule, bool b_presentation )
778 int64_t i_timestamp = -1;
780 if ( p_stream->b_oggds )
782 i_timestamp = i_granule * CLOCK_FREQ / p_stream->f_rate;
784 else if( p_stream->fmt.i_codec == VLC_CODEC_THEORA ||
785 p_stream->fmt.i_codec == VLC_CODEC_KATE )
787 ogg_int64_t iframe = i_granule >> p_stream->i_granule_shift;
788 ogg_int64_t pframe = i_granule - ( iframe << p_stream->i_granule_shift );
789 /* See Theora A.2.3 */
790 if ( b_presentation ) pframe -= p_stream->i_keyframe_offset;
791 i_timestamp = ( iframe + pframe ) * CLOCK_FREQ / p_stream->f_rate;
793 else if ( p_stream->fmt.i_codec == VLC_CODEC_VP8 )
795 ogg_int64_t frame = i_granule >> p_stream->i_granule_shift;
796 if ( b_presentation ) frame--;
797 i_timestamp = frame * CLOCK_FREQ / p_stream->f_rate;
799 else if( p_stream->fmt.i_codec == VLC_CODEC_DIRAC )
801 ogg_int64_t i_dts = i_granule >> 31;
802 /* NB, OggDirac granulepos values are in units of 2*picturerate */
803 i_timestamp = (i_dts/2) * CLOCK_FREQ / p_stream->f_rate;
805 else if( p_stream->fmt.i_codec == VLC_CODEC_OPUS )
807 i_timestamp = p_stream->i_pre_skip + i_granule * CLOCK_FREQ / 48000;
809 else if( p_stream->fmt.i_codec == VLC_CODEC_VORBIS )
811 i_timestamp = i_granule * CLOCK_FREQ / p_stream->f_rate;
817 bool Oggseek_PacketPCRFixup( logical_stream_t *p_stream, ogg_page *p_page,
818 ogg_packet *p_packet )
820 if ( p_packet->granulepos != -1 )
823 if ( p_stream->b_oggds )
825 p_stream->i_pcr = Oggseek_GranuleToAbsTimestamp( p_stream,
826 ogg_page_granulepos( p_page ), true );
829 else if ( p_stream->fmt.i_codec == VLC_CODEC_THEORA )
831 p_stream->i_pcr = Oggseek_GranuleToAbsTimestamp( p_stream,
832 ogg_page_granulepos( p_page ), false );
833 /* Computes the presentation time of the first packet on page */
834 p_stream->i_pcr -= CLOCK_FREQ *
835 ogg_page_packets( p_page ) / p_stream->f_rate;
838 else if ( p_stream->fmt.i_codec == VLC_CODEC_VP8 )
840 ogg_int64_t frame = ogg_page_granulepos( p_page ) >> p_stream->i_granule_shift;
841 frame -= ogg_page_packets( p_page );
842 p_stream->i_pcr = frame * CLOCK_FREQ / p_stream->f_rate;
849 static int64_t OggBisectSearchByTime( demux_t *p_demux, logical_stream_t *p_stream,
850 int64_t i_targettime, int64_t i_pos_lower, int64_t i_pos_upper)
861 } bestlower = { p_stream->i_data_start, -1, -1 },
862 current = { -1, -1, -1 };
864 demux_sys_t *p_sys = p_demux->p_sys;
866 i_pos_lower = __MAX( i_pos_lower, p_stream->i_data_start );
867 i_pos_upper = __MIN( i_pos_upper, p_sys->i_total_length );
868 if ( i_pos_upper < 0 ) i_pos_upper = p_sys->i_total_length;
870 i_start_pos = i_pos_lower;
871 i_end_pos = i_pos_upper;
873 i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
874 i_start_pos += i_segsize;
876 OggDebug( msg_Dbg(p_demux, "Bisecting for time=%"PRId64" between %"PRId64" and %"PRId64,
877 i_targettime, i_pos_lower, i_pos_upper ) );
881 /* see if the frame lies in current segment */
882 i_start_pos = __MAX( i_start_pos, i_pos_lower );
883 i_end_pos = __MIN( i_end_pos, i_pos_upper );
885 if ( i_start_pos >= i_end_pos )
887 if ( i_start_pos == i_pos_lower)
895 current.i_pos = find_first_page_granule( p_demux,
896 i_start_pos, i_end_pos,
898 ¤t.i_granule );
900 current.i_timestamp = Oggseek_GranuleToAbsTimestamp( p_stream,
901 current.i_granule, false );
903 if ( current.i_timestamp == -1 )
905 msg_Err( p_demux, "Unmatched granule. New codec ?" );
909 if ( current.i_pos != -1 && current.i_granule != -1 )
913 if ( current.i_timestamp <= i_targettime )
915 /* set our lower bound */
916 if ( current.i_timestamp > bestlower.i_timestamp )
918 i_start_pos = current.i_pos;
920 else if ( current.i_timestamp > i_targettime )
922 /* check lower half of segment */
923 i_start_pos -= i_segsize;
924 i_end_pos -= i_segsize;
929 /* no keyframe found, check lower segment */
930 i_end_pos -= i_segsize;
931 i_start_pos -= i_segsize;
934 OggDebug( msg_Dbg(p_demux, "Bisect restart i_segsize=%"PRId64" between %"PRId64" and %"PRId64,
935 i_segsize, i_start_pos, i_end_pos ) );
937 i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
938 i_start_pos += i_segsize;
940 } while ( i_segsize > 64 );
942 if ( bestlower.i_granule == -1 ) return -1;
944 if ( p_stream->b_oggds )
946 int64_t a = OggBackwardSeekToFrame( p_demux,
947 __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ),
949 p_stream, bestlower.i_granule /* unused */ );
952 /* If not each packet is usable as keyframe, query the codec for keyframe */
953 else if ( Ogg_GetKeyframeGranule( p_stream, bestlower.i_granule ) != bestlower.i_granule )
955 int64_t i_keyframegranule = Ogg_GetKeyframeGranule( p_stream, bestlower.i_granule );
957 OggDebug( msg_Dbg( p_demux, "Need to reseek to keyframe (%"PRId64") granule (%"PRId64"!=%"PRId64") to t=%"PRId64,
958 i_keyframegranule >> p_stream->i_granule_shift,
961 Oggseek_GranuleToAbsTimestamp( p_stream, i_keyframegranule, false ) ) );
963 OggDebug( msg_Dbg( p_demux, "Seeking back to %"PRId64, __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ) ) );
965 int64_t a = OggBackwardSeekToFrame( p_demux,
966 __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ),
967 stream_Size( p_demux->s ), p_stream, i_keyframegranule );
971 return bestlower.i_pos;
975 /************************************************************************
977 *************************************************************************/
979 int Oggseek_BlindSeektoAbsoluteTime( demux_t *p_demux, logical_stream_t *p_stream,
980 int64_t i_time, bool b_fastseek )
982 demux_sys_t *p_sys = p_demux->p_sys;
983 int64_t i_lowerpos = -1;
984 int64_t i_upperpos = -1;
985 bool b_found = false;
987 /* Search in skeleton */
988 Ogg_GetBoundsUsingSkeletonIndex( p_stream, i_time, &i_lowerpos, &i_upperpos );
989 if ( i_lowerpos != -1 ) b_found = true;
991 /* And also search in our own index */
992 if ( !b_found && OggSeekIndexFind( p_stream, i_time, &i_lowerpos, &i_upperpos ) )
997 /* Or try to be smart with audio fixed bitrate streams */
998 if ( !b_found && p_stream->fmt.i_cat == AUDIO_ES && p_sys->i_streams == 1
999 && p_sys->i_bitrate && Ogg_GetKeyframeGranule( p_stream, 0xFF00FF00 ) == 0xFF00FF00 )
1001 /* But only if there's no keyframe/preload requirements */
1002 /* FIXME: add function to get preload time by codec, ex: opus */
1003 i_lowerpos = i_time * p_sys->i_bitrate / INT64_C(8000000);
1008 if ( !b_found && b_fastseek )
1010 i_lowerpos = OggBisectSearchByTime( p_demux, p_stream, i_time,
1011 p_stream->i_data_start, p_sys->i_total_length );
1012 b_found = ( i_lowerpos != -1 );
1015 if ( !b_found ) return -1;
1017 if ( i_lowerpos < p_stream->i_data_start || i_upperpos > p_sys->i_total_length )
1020 /* And really do seek */
1021 p_sys->i_input_position = i_lowerpos;
1022 seek_byte( p_demux, p_sys->i_input_position );
1023 ogg_stream_reset( &p_stream->os );
1028 int Oggseek_BlindSeektoPosition( demux_t *p_demux, logical_stream_t *p_stream,
1029 double f, bool b_canfastseek )
1031 OggDebug( msg_Dbg( p_demux, "=================== Seeking To Blind Pos" ) );
1032 int64_t i_size = stream_Size( p_demux->s );
1036 i_size = find_first_page_granule( p_demux,
1041 OggDebug( msg_Dbg( p_demux, "Seek start pos is %"PRId64" granule %"PRId64, i_size, i_granule ) );
1043 i_granule = Ogg_GetKeyframeGranule( p_stream, i_granule );
1045 if ( b_canfastseek )
1047 /* Peek back until we meet a keyframe to start our decoding up to our
1048 * final seek time */
1049 i_pagepos = OggBackwardSeekToFrame( p_demux,
1050 __MAX ( i_size - MAX_PAGE_SIZE, p_stream->i_data_start ),
1051 __MIN ( i_size + MAX_PAGE_SIZE, p_demux->p_sys->i_total_length ),
1052 p_stream, i_granule );
1056 /* Otherwise, we just sync to the next keyframe we meet */
1057 i_pagepos = OggForwardSeekToFrame( p_demux,
1058 __MAX ( i_size - MAX_PAGE_SIZE, p_stream->i_data_start ),
1059 stream_Size( p_demux->s ),
1060 p_stream, i_granule, false );
1063 OggDebug( msg_Dbg( p_demux, "=================== Seeked To %"PRId64" granule %"PRId64, i_pagepos, i_granule ) );
1067 int Oggseek_SeektoAbsolutetime( demux_t *p_demux, logical_stream_t *p_stream,
1070 demux_sys_t *p_sys = p_demux->p_sys;
1072 OggDebug( msg_Dbg( p_demux, "=================== Seeking To Absolute Time %"PRId64, i_time ) );
1073 int64_t i_offset_lower = -1;
1074 int64_t i_offset_upper = -1;
1076 if ( Ogg_GetBoundsUsingSkeletonIndex( p_stream, i_time, &i_offset_lower, &i_offset_upper ) )
1079 OggDebug( msg_Dbg( p_demux, "Found keyframe at %"PRId64" using skeleton index", i_offset_lower ) );
1080 if ( i_offset_lower == -1 ) i_offset_lower = p_stream->i_data_start;
1081 p_sys->i_input_position = i_offset_lower;
1082 seek_byte( p_demux, p_sys->i_input_position );
1083 ogg_stream_reset( &p_stream->os );
1084 return i_offset_lower;
1086 OggDebug( msg_Dbg( p_demux, "Search bounds set to %"PRId64" %"PRId64" using skeleton index", i_offset_lower, i_offset_upper ) );
1089 OggSeekIndexFind( p_stream, i_time, &i_offset_lower, &i_offset_upper )
1092 i_offset_lower = __MAX( i_offset_lower, p_stream->i_data_start );
1093 i_offset_upper = __MIN( i_offset_upper, p_sys->i_total_length );
1095 int64_t i_pagepos = OggBisectSearchByTime( p_demux, p_stream, i_time,
1096 i_offset_lower, i_offset_upper);
1098 /* Insert keyframe position into index */
1100 if ( i_pagepos >= p_stream->i_data_start )
1101 OggSeek_IndexAdd( p_stream, i_time, i_pagepos )
1104 OggDebug( msg_Dbg( p_demux, "=================== Seeked To %"PRId64" time %"PRId64, i_pagepos, i_time ) );
1108 /****************************************************************************
1109 * oggseek_read_page: Read a full Ogg page from the physical bitstream.
1110 ****************************************************************************
1111 * Returns number of bytes read. This should always be > 0
1112 * unless we are at the end of stream.
1114 ****************************************************************************/
1116 int64_t oggseek_read_page( demux_t *p_demux )
1118 demux_sys_t *p_ogg = p_demux->p_sys ;
1119 uint8_t header[PAGE_HEADER_BYTES+255];
1127 demux_sys_t *p_sys = p_demux->p_sys;
1129 /* store position of this page */
1130 i_in_pos = p_ogg->i_input_position = stream_Tell( p_demux->s );
1132 if ( p_sys->b_page_waiting) {
1133 msg_Warn( p_demux, "Ogg page already loaded" );
1137 if ( stream_Read ( p_demux->s, header, PAGE_HEADER_BYTES ) < PAGE_HEADER_BYTES )
1139 stream_Seek( p_demux->s, i_in_pos );
1140 msg_Dbg ( p_demux, "Reached clean EOF in ogg file" );
1144 i_nsegs = header[ PAGE_HEADER_BYTES - 1 ];
1146 if ( stream_Read ( p_demux->s, header+PAGE_HEADER_BYTES, i_nsegs ) < i_nsegs )
1148 stream_Seek( p_demux->s, i_in_pos );
1149 msg_Warn ( p_demux, "Reached broken EOF in ogg file" );
1153 i_page_size = PAGE_HEADER_BYTES + i_nsegs;
1155 for ( i = 0; i < i_nsegs; i++ )
1157 i_page_size += header[ PAGE_HEADER_BYTES + i ];
1160 ogg_sync_reset( &p_ogg->oy );
1162 buf = ogg_sync_buffer( &p_ogg->oy, i_page_size );
1164 memcpy( buf, header, PAGE_HEADER_BYTES + i_nsegs );
1166 i_result = stream_Read ( p_demux->s, (uint8_t*)buf + PAGE_HEADER_BYTES + i_nsegs,
1167 i_page_size - PAGE_HEADER_BYTES - i_nsegs );
1169 ogg_sync_wrote( &p_ogg->oy, i_result + PAGE_HEADER_BYTES + i_nsegs );
1174 if ( ogg_sync_pageout( &p_ogg->oy, &p_ogg->current_page ) != 1 )
1176 msg_Err( p_demux , "Got invalid packet, read %"PRId64" of %i: %s %"PRId64,
1177 i_result, i_page_size, buf, i_in_pos );
1181 p_sys->b_page_waiting = false;
1183 return i_result + PAGE_HEADER_BYTES + i_nsegs;