Coverage Report

Created: 2025-12-14 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/demux/oggseek.c
Line
Count
Source
1
/*****************************************************************************
2
 * oggseek.c : ogg seeking functions for ogg demuxer vlc
3
 *****************************************************************************
4
 * Copyright (C) 2008 - 2010 Gabriel Finch <salsaman@gmail.com>
5
 *
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
9
 *
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.
14
 *
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.
19
 *
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
 *****************************************************************************/
24
25
/*****************************************************************************
26
 * Preamble
27
 *****************************************************************************/
28
29
#ifdef HAVE_CONFIG_H
30
# include "config.h"
31
#endif
32
33
#ifdef HAVE_LIBVORBIS
34
  #include <vorbis/codec.h>
35
#endif
36
37
#include <vlc_common.h>
38
#include <vlc_demux.h>
39
40
#include <ogg/ogg.h>
41
#include <limits.h>
42
#include <math.h>
43
#include <assert.h>
44
45
#include "ogg.h"
46
#include "oggseek.h"
47
#include "ogg_granule.h"
48
49
0
#define SEGMENT_NOT_FOUND -1
50
51
12.4k
#define MAX_PAGE_SIZE 65307
52
#define MIN_PAGE_SIZE 27
53
typedef struct packetStartCoordinates
54
{
55
    int64_t i_pos;
56
    int64_t i_pageno;
57
    int64_t i_skip;
58
} packetStartCoordinates;
59
60
/************************************************************
61
* index entries
62
*************************************************************/
63
64
/* free all entries in index list */
65
66
void oggseek_index_entries_free ( demux_index_entry_t *idx )
67
0
{
68
0
    demux_index_entry_t *idx_next;
69
70
0
    while ( idx != NULL )
71
0
    {
72
0
        idx_next = idx->p_next;
73
0
        free( idx );
74
0
        idx = idx_next;
75
0
    }
76
0
}
77
78
79
/* internal function to create a new list member */
80
81
static demux_index_entry_t *index_entry_new( vlc_tick_t i_timestamp, int64_t i_pagepos )
82
0
{
83
0
    if ( i_timestamp == VLC_TICK_INVALID || i_pagepos < 1 )
84
0
        return NULL;
85
86
0
    demux_index_entry_t *idx = malloc( sizeof(*idx) );
87
0
    if ( idx )
88
0
    {
89
0
        idx->i_value = i_timestamp;
90
0
        idx->i_pagepos = i_pagepos;
91
0
        idx->p_next = NULL;
92
0
    }
93
0
    return idx;
94
0
}
95
96
/* We insert into index, sorting by pagepos (as a page can match multiple
97
   time stamps) */
98
const demux_index_entry_t *OggSeek_IndexAdd ( logical_stream_t *p_stream,
99
                                             vlc_tick_t i_timestamp,
100
                                             int64_t i_pagepos )
101
0
{
102
0
    demux_index_entry_t **pp_next = &p_stream->idx;
103
0
    for( ; *pp_next; )
104
0
    {
105
0
        if( (*pp_next)->i_pagepos >= i_pagepos )
106
0
        {
107
0
            if( (*pp_next)->i_pagepos == i_pagepos )
108
0
                return NULL;
109
0
            break;
110
0
        }
111
0
        pp_next = &((*pp_next)->p_next);
112
0
    }
113
114
0
    demux_index_entry_t *ie = index_entry_new( i_timestamp, i_pagepos );
115
0
    if ( ie )
116
0
    {
117
0
        ie->p_next = *pp_next;
118
0
        *pp_next = ie;
119
0
    }
120
121
0
    return ie;
122
0
}
123
124
static bool OggSeekIndexFind ( logical_stream_t *p_stream, vlc_tick_t i_timestamp,
125
                               int64_t *pi_pos_lower, int64_t *pi_pos_upper,
126
                               vlc_tick_t *pi_lower_timestamp )
127
0
{
128
0
    demux_index_entry_t *idx = p_stream->idx;
129
130
0
    while ( idx != NULL )
131
0
    {
132
0
        if ( idx->i_value <= i_timestamp )
133
0
        {
134
0
            if ( !idx->p_next ) /* found on last index */
135
0
            {
136
0
                *pi_pos_lower = idx->i_pagepos;
137
0
                *pi_lower_timestamp = idx->i_value;
138
0
                return true;
139
0
            }
140
0
            if ( idx->p_next->i_value > i_timestamp )
141
0
            {
142
0
                *pi_pos_lower = idx->i_pagepos;
143
0
                *pi_lower_timestamp = idx->i_value;
144
0
                *pi_pos_upper = idx->p_next->i_pagepos;
145
0
                return true;
146
0
            }
147
0
        }
148
0
        idx = idx->p_next;
149
0
    }
150
151
0
    return false;
152
0
}
153
154
/*********************************************************************
155
 * private functions
156
 **********************************************************************/
157
158
/* seek in ogg file to offset i_pos and update the sync */
159
160
static void seek_byte( demux_t *p_demux, int64_t i_pos )
161
0
{
162
0
    demux_sys_t *p_sys  = p_demux->p_sys;
163
164
0
    if ( ! vlc_stream_Seek( p_demux->s, i_pos ) )
165
0
    {
166
0
        ogg_sync_reset( &p_sys->oy );
167
168
0
        p_sys->i_input_position = i_pos;
169
0
        p_sys->b_page_waiting = false;
170
0
    }
171
0
}
172
173
174
175
/* read bytes from the ogg file to try to find a page start */
176
177
static int64_t get_data( demux_t *p_demux, int64_t i_bytes_to_read )
178
0
{
179
0
    demux_sys_t *p_sys  = p_demux->p_sys;
180
181
0
    char *buf;
182
0
    int64_t i_result;
183
184
0
    if ( p_sys->i_total_bytes > 0 )
185
0
    {
186
0
        if ( p_sys->i_input_position + i_bytes_to_read > p_sys->i_total_bytes )
187
0
        {
188
0
            i_bytes_to_read = p_sys->i_total_bytes - p_sys->i_input_position;
189
0
            if ( i_bytes_to_read <= 0 ) {
190
0
                return 0;
191
0
            }
192
0
        }
193
0
    }
194
195
0
    i_bytes_to_read = __MIN( i_bytes_to_read, INT_MAX );
196
197
0
    seek_byte ( p_demux, p_sys->i_input_position );
198
199
0
    buf = ogg_sync_buffer( &p_sys->oy, i_bytes_to_read );
200
0
    if( !buf )
201
0
        return 0;
202
203
0
    i_result = vlc_stream_Read( p_demux->s, buf, i_bytes_to_read );
204
205
0
    ogg_sync_wrote( &p_sys->oy, i_result );
206
0
    return i_result;
207
0
}
208
209
210
void Oggseek_ProbeEnd( demux_t *p_demux )
211
4.90k
{
212
    /* Temporary state */
213
4.90k
    ogg_stream_state os;
214
4.90k
    ogg_sync_state oy;
215
4.90k
    ogg_page page;
216
4.90k
    demux_sys_t *p_sys = p_demux->p_sys;
217
4.90k
    int64_t i_pos, i_startpos, i_result, i_granule, i_lowerbound;
218
4.90k
    int64_t i_length = 0;
219
4.90k
    int64_t i_backup_pos = vlc_stream_Tell( p_demux->s );
220
4.90k
    int64_t i_upperbound = stream_Size( p_demux->s );
221
4.90k
    unsigned int i_backoffset = OGGSEEK_BYTES_TO_READ;
222
4.90k
    assert( OGGSEEK_BYTES_TO_READ < UINT_MAX );
223
224
4.90k
    const char *buffer;
225
226
4.90k
    ogg_stream_init( &os, -1 );
227
4.90k
    ogg_sync_init( &oy );
228
229
    /* Try to lookup last granule from each logical stream */
230
4.90k
    i_lowerbound = stream_Size( p_demux->s ) - p_sys->i_streams * MAX_PAGE_SIZE * 2;
231
4.90k
    i_lowerbound = __MAX( 0, i_lowerbound );
232
233
4.90k
    i_pos = i_startpos = __MAX( i_lowerbound, i_upperbound - i_backoffset );
234
235
4.90k
    if ( vlc_stream_Seek( p_demux->s, i_pos ) )
236
0
    {
237
0
        ogg_sync_clear( &oy );
238
0
        ogg_stream_clear( &os );
239
0
        return;
240
0
    }
241
242
11.0k
    while( i_pos >= i_lowerbound )
243
11.0k
    {
244
245
32.1k
        while( i_pos < i_upperbound )
246
21.1k
        {
247
21.1k
            if ( oy.unsynced )
248
17.3k
                i_result = ogg_sync_pageseek( &oy, &page );
249
250
21.1k
            buffer = ogg_sync_buffer( &oy, OGGSEEK_BYTES_TO_READ );
251
21.1k
            if ( buffer == NULL ) goto clean;
252
21.1k
            i_result = vlc_stream_Read( p_demux->s, (void*) buffer, OGGSEEK_BYTES_TO_READ );
253
21.1k
            if ( i_result < 1 ) goto clean;
254
21.1k
            i_pos += i_result;
255
21.1k
            ogg_sync_wrote( &oy, i_result );
256
257
75.1k
            while( ogg_sync_pageout( &oy, &page ) == 1 )
258
54.0k
            {
259
54.0k
                i_granule = ogg_page_granulepos( &page );
260
54.0k
                if ( i_granule == -1 ) continue;
261
262
166k
                for ( int i=0; i< p_sys->i_streams; i++ )
263
135k
                {
264
135k
                    if ( p_sys->pp_stream[i]->i_serial_no != ogg_page_serialno( &page ) )
265
113k
                        continue;
266
267
21.9k
                    i_length = Ogg_GranuleToTime( p_sys->pp_stream[i], i_granule,
268
21.9k
                                                  !p_sys->pp_stream[i]->b_contiguous, false );
269
21.9k
                    if( i_length != VLC_TICK_INVALID )
270
6.06k
                    {
271
                        /* We found at least a page with valid granule */
272
6.06k
                        p_sys->i_length = __MAX( p_sys->i_length, i_length - VLC_TICK_0 );
273
6.06k
                    }
274
21.9k
                    break;
275
135k
                }
276
53.2k
            }
277
21.1k
        }
278
279
11.0k
        if( i_startpos == i_lowerbound ||
280
7.41k
            p_sys->i_length != VLC_TICK_INVALID )
281
4.90k
            goto clean;
282
283
6.10k
        int64_t i_next_upperbound = __MIN(i_startpos + MIN_PAGE_SIZE, i_upperbound);
284
285
        /* Otherwise increase read size, starting earlier */
286
6.10k
        if ( i_backoffset <= MAX_PAGE_SIZE )
287
4.68k
        {
288
4.68k
            i_backoffset <<= 1;
289
4.68k
            i_startpos = i_upperbound - i_backoffset;
290
4.68k
        }
291
1.42k
        else
292
1.42k
        {
293
1.42k
            i_startpos = i_upperbound - MAX_PAGE_SIZE;
294
1.42k
        }
295
296
6.10k
        i_upperbound = i_next_upperbound;
297
298
6.10k
        i_startpos = __MAX( i_startpos, i_lowerbound );
299
6.10k
        i_pos = i_startpos;
300
301
6.10k
        if ( vlc_stream_Seek( p_demux->s, i_pos ) )
302
0
            break;
303
6.10k
    }
304
305
4.90k
clean:
306
4.90k
    vlc_stream_Seek( p_demux->s, i_backup_pos );
307
308
4.90k
    ogg_sync_clear( &oy );
309
4.90k
    ogg_stream_clear( &os );
310
4.90k
}
311
312
static void update_page_type( logical_stream_t *p_stream, int64_t i_granule )
313
0
{
314
0
    if( i_granule == 0 )
315
0
        p_stream->page_type = OGGPAGE_HEADER;
316
0
    else if( p_stream->page_type == OGGPAGE_HEADER )
317
0
        p_stream->page_type = OGGPAGE_FIRST;
318
0
    else
319
0
        p_stream->page_type = OGGPAGE_OTHER;
320
0
}
321
322
static int64_t find_first_page_granule( demux_t *p_demux,
323
                                int64_t i_pos1, int64_t i_pos2,
324
                                logical_stream_t *p_stream,
325
                                int64_t *i_granulepos )
326
0
{
327
0
    int64_t i_result;
328
0
    *i_granulepos = -1;
329
0
    int64_t i_bytes_to_read = i_pos2 - i_pos1 + 1;
330
0
    int64_t i_bytes_read;
331
0
    int64_t i_packets_checked;
332
333
0
    demux_sys_t *p_sys  = p_demux->p_sys;
334
335
0
    ogg_packet op;
336
337
0
    seek_byte( p_demux, i_pos1 );
338
339
0
    if ( i_bytes_to_read > OGGSEEK_BYTES_TO_READ ) i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
340
341
0
    while ( 1 )
342
0
    {
343
344
0
        if ( p_sys->i_input_position >= i_pos2 )
345
0
        {
346
            /* we reached the end and found no pages */
347
0
            return -1;
348
0
        }
349
350
        /* read next chunk */
351
0
        if ( ! ( i_bytes_read = get_data( p_demux, i_bytes_to_read ) ) )
352
0
        {
353
            /* EOF */
354
0
            return -1;
355
0
        }
356
357
0
        i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
358
359
0
        i_result = ogg_sync_pageseek( &p_sys->oy, &p_sys->current_page );
360
361
0
        if ( i_result < 0 )
362
0
        {
363
            /* found a page, sync to page start */
364
0
            p_sys->i_input_position -= i_result;
365
0
            i_pos1 = p_sys->i_input_position;
366
0
            continue;
367
0
        }
368
369
0
        if ( i_result > 0 || ( i_result == 0 && p_sys->oy.fill > 3 &&
370
0
                               ! strncmp( (char *)p_sys->oy.data, "OggS" , 4 ) ) )
371
0
        {
372
0
            i_pos1 = p_sys->i_input_position;
373
0
            break;
374
0
        }
375
376
0
        p_sys->i_input_position += i_bytes_read;
377
378
0
    };
379
380
0
    seek_byte( p_demux, p_sys->i_input_position );
381
0
    ogg_stream_reset( &p_stream->os );
382
383
    /* prevent reading the whole file if stream is gone */
384
0
    if( i_pos2 > p_sys->i_input_position + OGGSEEK_SERIALNO_MAX_LOOKUP_BYTES )
385
0
        i_pos2 = p_sys->i_input_position + OGGSEEK_SERIALNO_MAX_LOOKUP_BYTES;
386
387
0
    while( 1 )
388
0
    {
389
390
0
        if ( p_sys->i_input_position >= i_pos2 )
391
0
        {
392
            /* reached the end of the search region and nothing was found */
393
0
            return p_sys->i_input_position;
394
0
        }
395
396
0
        p_sys->b_page_waiting = false;
397
398
0
        if ( ! ( i_result = oggseek_read_page( p_demux ) ) )
399
0
        {
400
            /* EOF */
401
0
            return p_sys->i_input_position;
402
0
        }
403
404
        // found a page
405
0
        if ( ogg_stream_pagein( &p_stream->os, &p_sys->current_page ) != 0 )
406
0
        {
407
            /* page is not for this stream or incomplete */
408
0
            p_sys->i_input_position += i_result;
409
0
            continue;
410
0
        }
411
412
0
        if ( ogg_page_granulepos( &p_sys->current_page ) <= 0 )
413
0
        {
414
0
            update_page_type( p_stream, ogg_page_granulepos(( &p_sys->current_page )) );
415
            /* A negative granulepos means that the packet continues on the
416
             * next page => read the next page */
417
0
            p_sys->i_input_position += i_result;
418
0
            continue;
419
0
        }
420
421
0
        i_packets_checked = 0;
422
423
0
        while ( ogg_stream_packetout( &p_stream->os, &op ) > 0 )
424
0
        {
425
0
            i_packets_checked++;
426
0
        }
427
428
0
        if ( i_packets_checked )
429
0
        {
430
0
            *i_granulepos = ogg_page_granulepos( &p_sys->current_page );
431
0
            return p_sys->i_input_position;
432
0
        }
433
434
        /*  -> start of next page */
435
0
        p_sys->i_input_position += i_result;
436
0
    }
437
0
}
438
439
static bool OggSeekToPacket( demux_t *p_demux, logical_stream_t *p_stream,
440
            int64_t i_granulepos, packetStartCoordinates *p_lastpacketcoords,
441
            bool b_exact )
442
0
{
443
0
    ogg_packet op;
444
0
    demux_sys_t *p_sys  = p_demux->p_sys;
445
0
    if ( ogg_stream_pagein( &p_stream->os, &p_sys->current_page ) != 0 )
446
0
        return false;
447
0
    p_sys->b_page_waiting = true;
448
0
    int i=0;
449
450
0
    update_page_type( p_stream, ogg_page_granulepos( &p_sys->current_page ) );
451
452
0
    int64_t itarget_frame = Ogg_GetKeyframeGranule( p_stream, i_granulepos );
453
0
    int64_t iframe = Ogg_GetKeyframeGranule( p_stream, ogg_page_granulepos( &p_sys->current_page ) );
454
455
0
    if ( ! ogg_page_continued( &p_sys->current_page ) )
456
0
    {
457
        /* Start of frame, not continued page, but no packet. */
458
0
        p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
459
0
        p_lastpacketcoords->i_pos = p_sys->i_input_position;
460
0
        p_lastpacketcoords->i_skip = 0;
461
0
    }
462
463
0
    if ( b_exact && iframe > itarget_frame )
464
0
    {
465
0
        while( ogg_stream_packetout( &p_stream->os, &op ) > 0 ) {};
466
0
        p_sys->b_page_waiting = false;
467
0
        return false;
468
0
    }
469
470
0
    while( ogg_stream_packetpeek( &p_stream->os, &op ) > 0 )
471
0
    {
472
0
        if ( ( !b_exact || itarget_frame == iframe ) && Ogg_IsKeyFrame( p_stream, &op ) )
473
0
        {
474
0
            OggDebug(
475
0
                msg_Dbg(p_demux, "** KEYFRAME **" );
476
0
                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 );
477
0
                msg_Dbg(p_demux, "KEYFRAME PACKET IS at pageno %"PRId64" OFFSET %"PRId64" with skip %d packet (%d / %d) ",
478
0
                    ogg_page_pageno( &p_sys->current_page ), p_sys->i_input_position, i, i+1, ogg_page_packets( &p_sys->current_page ) );
479
0
            );
480
481
0
            if ( i != 0 ) /* Not continued packet */
482
0
            {
483
                /* We need to handle the case when the packet spans onto N
484
                       previous page(s). packetout() will be valid only when
485
                       all segments are assembled.
486
                       Keyframe flag is only available after assembling last part
487
                       (when packetout() becomes valid). We have no way to guess
488
                       keyframe at earlier time.
489
                    */
490
0
                p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
491
0
                p_lastpacketcoords->i_pos = p_sys->i_input_position;
492
0
                p_lastpacketcoords->i_skip = i;
493
0
            }
494
0
            return true;
495
0
        }
496
497
0
        p_lastpacketcoords->i_pageno = ogg_page_pageno( &p_sys->current_page );
498
0
        p_lastpacketcoords->i_pos = p_sys->i_input_position;
499
0
        p_lastpacketcoords->i_skip = i + 1;
500
0
        i++;
501
        /* remove that packet and go sync to next */
502
0
        ogg_stream_packetout( &p_stream->os, &op );
503
0
    }
504
505
0
    return false;
506
0
}
507
508
static int64_t OggForwardSeekToFrame( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2,
509
                logical_stream_t *p_stream, int64_t i_granulepos, bool b_fastseek )
510
0
{
511
0
    int64_t i_result;
512
0
    int64_t i_bytes_to_read;
513
0
    int64_t i_bytes_read;
514
515
0
    demux_sys_t *p_sys  = p_demux->p_sys;
516
517
0
    i_bytes_to_read = i_pos2 - i_pos1 + 1;
518
0
    seek_byte( p_demux, i_pos1 );
519
0
    if ( i_bytes_to_read > OGGSEEK_BYTES_TO_READ ) i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
520
521
0
    OggDebug(
522
0
        msg_Dbg( p_demux, "Probing Fwd %"PRId64" %"PRId64" for granule %"PRId64,
523
0
        i_pos1, i_pos2, i_granulepos );
524
0
    );
525
526
0
    while ( 1 )
527
0
    {
528
529
0
        if ( p_sys->i_input_position >= i_pos2 )
530
0
            return SEGMENT_NOT_FOUND;
531
532
        /* read next chunk */
533
0
        if ( ! ( i_bytes_read = get_data( p_demux, i_bytes_to_read ) ) )
534
0
            return SEGMENT_NOT_FOUND;
535
536
0
        i_bytes_to_read = OGGSEEK_BYTES_TO_READ;
537
538
0
        i_result = ogg_sync_pageseek( &p_sys->oy, &p_sys->current_page );
539
540
0
        if ( i_result < 0 )
541
0
        {
542
            /* found a page, sync to page start */
543
0
            p_sys->i_input_position -= i_result;
544
0
            i_pos1 = p_sys->i_input_position;
545
0
            continue;
546
0
        }
547
548
0
        if ( i_result > 0 || ( i_result == 0 && p_sys->oy.fill > 3 &&
549
0
                               ! strncmp( (char *)p_sys->oy.data, "OggS" , 4 ) ) )
550
0
        {
551
0
            i_pos1 = p_sys->i_input_position;
552
0
            break;
553
0
        }
554
555
0
        p_sys->i_input_position += i_bytes_read;
556
0
    };
557
558
0
    seek_byte( p_demux, p_sys->i_input_position );
559
0
    ogg_stream_reset( &p_stream->os );
560
561
0
    ogg_packet op;
562
0
    while( ogg_stream_packetout( &p_stream->os, &op ) > 0 ) {};
563
564
0
    packetStartCoordinates lastpacket = { -1, -1, -1 };
565
566
0
    while( 1 )
567
0
    {
568
569
0
        if ( p_sys->i_input_position >= i_pos2 )
570
0
        {
571
            /* reached the end of the search region and nothing was found */
572
0
            break;
573
0
        }
574
575
0
        p_sys->b_page_waiting = false;
576
577
0
        if ( ! ( i_result = oggseek_read_page( p_demux ) ) )
578
0
        {
579
            /* EOF */
580
0
            break;
581
0
        }
582
583
        // found a page
584
0
        if ( p_stream->os.serialno != ogg_page_serialno( &p_sys->current_page ) )
585
0
        {
586
            /* page is not for this stream */
587
0
            p_sys->i_input_position += i_result;
588
0
            continue;
589
0
        }
590
591
0
        if ( OggSeekToPacket( p_demux, p_stream, i_granulepos, &lastpacket, b_fastseek ) )
592
0
        {
593
0
            p_sys->i_input_position = lastpacket.i_pos;
594
0
            p_stream->i_skip_frames = 0;
595
0
            return p_sys->i_input_position;
596
0
        }
597
598
        /*  -> start of next page */
599
0
        p_sys->i_input_position += i_result;
600
0
    }
601
602
0
    return SEGMENT_NOT_FOUND;
603
0
}
604
605
static int64_t OggBackwardSeekToFrame( demux_t *p_demux, int64_t i_pos1, int64_t i_pos2,
606
                               logical_stream_t *p_stream, int64_t i_granulepos )
607
0
{
608
0
    int64_t i_result;
609
0
    int64_t i_offset = __MAX( 1 + ( (i_pos2 - i_pos1) >> 1 ), OGGSEEK_BYTES_TO_READ );
610
611
0
restart:
612
613
0
    OggDebug(
614
0
        msg_Dbg( p_demux, "Probing Back %"PRId64" %"PRId64" for granule %"PRId64,
615
0
        i_pos1, i_pos2, i_granulepos );
616
0
    );
617
618
0
    i_result = OggForwardSeekToFrame( p_demux, i_pos1, i_pos2, p_stream,
619
0
                                      i_granulepos, true );
620
621
0
    if ( i_result == SEGMENT_NOT_FOUND && i_pos1 > p_stream->i_data_start )
622
0
    {
623
0
        i_pos1 = __MAX( p_stream->i_data_start, i_pos1 - i_offset );
624
0
        goto restart;
625
0
    }
626
627
0
    return i_result;
628
0
}
629
630
/* returns pos */
631
static int64_t OggBisectSearchByTime( demux_t *p_demux, logical_stream_t *p_stream,
632
            vlc_tick_t i_targettime, int64_t i_pos_lower, int64_t i_pos_upper, int64_t *pi_seek_time)
633
0
{
634
0
    int64_t i_start_pos;
635
0
    int64_t i_end_pos;
636
0
    int64_t i_segsize;
637
638
0
    struct
639
0
    {
640
0
        int64_t i_pos;
641
0
        vlc_tick_t i_timestamp;
642
0
        int64_t i_granule;
643
0
    } bestlower = { p_stream->i_data_start, VLC_TICK_INVALID, -1 },
644
0
      current = { -1, VLC_TICK_INVALID, -1 },
645
0
      lowestupper = { -1, VLC_TICK_INVALID, -1 };
646
647
0
    demux_sys_t *p_sys  = p_demux->p_sys;
648
649
0
    i_pos_lower = __MAX( i_pos_lower, p_stream->i_data_start );
650
0
    i_pos_upper = __MIN( i_pos_upper, p_sys->i_total_bytes );
651
0
    if ( i_pos_upper < 0 ) i_pos_upper = p_sys->i_total_bytes;
652
653
0
    i_start_pos = i_pos_lower;
654
0
    i_end_pos = i_pos_upper;
655
656
0
    i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
657
0
    i_start_pos += i_segsize;
658
659
0
    OggDebug( msg_Dbg(p_demux, "Bisecting for time=%"PRId64" between %"PRId64" and %"PRId64,
660
0
            i_targettime, i_pos_lower, i_pos_upper ) );
661
662
    /* Check lowest possible bound that will never be checked in bisection */
663
0
    current.i_pos = find_first_page_granule( p_demux,
664
0
                                             i_pos_lower,
665
0
                                             __MIN(i_start_pos + PAGE_HEADER_BYTES, i_end_pos),
666
0
                                             p_stream,
667
0
                                             &current.i_granule );
668
0
    if( current.i_granule != -1 )
669
0
    {
670
0
        current.i_timestamp = Ogg_GranuleToTime( p_stream, current.i_granule,
671
0
                                                 !p_stream->b_contiguous, false );
672
0
        if( current.i_timestamp <= i_targettime )
673
0
            bestlower = current;
674
0
        else
675
0
            lowestupper = current;
676
0
    }
677
678
0
    do
679
0
    {
680
        /* see if the frame lies in current segment */
681
0
        i_start_pos = __MAX( i_start_pos, i_pos_lower );
682
0
        i_end_pos = __MIN( i_end_pos, i_pos_upper );
683
684
0
        if ( i_start_pos >= i_end_pos )
685
0
        {
686
0
            if ( i_start_pos == i_pos_lower)
687
0
            {
688
0
                return i_start_pos;
689
0
            }
690
0
            return -1;
691
0
        }
692
693
694
0
        current.i_pos = find_first_page_granule( p_demux,
695
0
                                                 i_start_pos, i_end_pos,
696
0
                                                 p_stream,
697
0
                                                 &current.i_granule );
698
699
0
        current.i_timestamp = Ogg_GranuleToTime( p_stream, current.i_granule,
700
0
                                                 !p_stream->b_contiguous, false );
701
702
0
        if ( current.i_timestamp == VLC_TICK_INVALID && current.i_granule > 0 )
703
0
        {
704
0
            msg_Err( p_demux, "Unmatched granule. New codec ?" );
705
0
            return -1;
706
0
        }
707
0
        else if ( current.i_timestamp < 0 )  /* due to preskip with some codecs */
708
0
        {
709
0
            current.i_timestamp = 0;
710
0
        }
711
712
0
        if ( current.i_pos != -1 && current.i_granule != -1 )
713
0
        {
714
            /* found a page */
715
716
0
            if ( current.i_timestamp <= i_targettime )
717
0
            {
718
                /* set our lower bound */
719
0
                if ( current.i_timestamp > bestlower.i_timestamp )
720
0
                    bestlower = current;
721
0
                i_start_pos = current.i_pos;
722
0
            }
723
0
            else
724
0
            {
725
0
                if ( lowestupper.i_timestamp == VLC_TICK_INVALID ||
726
0
                     current.i_timestamp < lowestupper.i_timestamp )
727
0
                    lowestupper = current;
728
                /* check lower half of segment */
729
0
                i_start_pos -= i_segsize;
730
0
                i_end_pos -= i_segsize;
731
0
            }
732
0
        }
733
0
        else
734
0
        {
735
            /* no keyframe found, check lower segment */
736
0
            i_end_pos -= i_segsize;
737
0
            i_start_pos -= i_segsize;
738
0
        }
739
740
0
        OggDebug( msg_Dbg(p_demux, "Bisect restart i_segsize=%"PRId64" between %"PRId64
741
0
                                   " and %"PRId64 " bl %"PRId64" lu %"PRId64,
742
0
                i_segsize, i_start_pos, i_end_pos, bestlower.i_granule, lowestupper.i_granule  ) );
743
744
0
        i_segsize = ( i_end_pos - i_start_pos + 1 ) >> 1;
745
0
        i_start_pos += i_segsize;
746
747
0
    } while ( i_segsize > PAGE_HEADER_BYTES );
748
749
0
    if ( bestlower.i_granule == -1 )
750
0
    {
751
0
        if ( lowestupper.i_granule == -1 )
752
0
            return -1;
753
0
        else
754
0
            bestlower = lowestupper;
755
0
    }
756
757
0
    if ( p_stream->b_oggds )
758
0
    {
759
0
        int64_t a = OggBackwardSeekToFrame( p_demux,
760
0
                __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ),
761
0
                bestlower.i_pos,
762
0
                p_stream, bestlower.i_granule /* unused */ );
763
0
        *pi_seek_time = bestlower.i_timestamp;
764
0
        return a;
765
0
    }
766
    /* If not each packet is usable as keyframe, query the codec for keyframe */
767
0
    else if ( Ogg_GetKeyframeGranule( p_stream, bestlower.i_granule ) != bestlower.i_granule )
768
0
    {
769
0
        int64_t i_keyframegranule = Ogg_GetKeyframeGranule( p_stream, bestlower.i_granule );
770
771
0
        OggDebug( msg_Dbg( p_demux, "Need to reseek to keyframe (%"PRId64") granule (%"PRId64"!=%"PRId64") to t=%"PRId64,
772
0
                           i_keyframegranule >> p_stream->i_granule_shift,
773
0
                           bestlower.i_granule,
774
0
                           i_pos_upper,
775
0
                           Ogg_GranuleToTime( p_stream, i_keyframegranule, !p_stream->b_contiguous, false ) ) );
776
777
0
        OggDebug( msg_Dbg( p_demux, "Seeking back to %"PRId64, __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ) ) );
778
779
0
        int64_t a = OggBackwardSeekToFrame( p_demux,
780
0
            __MAX ( bestlower.i_pos - OGGSEEK_BYTES_TO_READ, p_stream->i_data_start ),
781
0
            stream_Size( p_demux->s ), p_stream, i_keyframegranule );
782
0
       *pi_seek_time = Ogg_GranuleToTime( p_stream, i_keyframegranule,
783
0
                                          !p_stream->b_contiguous, false );
784
0
        return a;
785
0
    }
786
787
0
    *pi_seek_time = bestlower.i_timestamp;
788
0
    return bestlower.i_pos;
789
0
}
790
791
792
/************************************************************************
793
 * public functions
794
 *************************************************************************/
795
796
int Oggseek_BlindSeektoAbsoluteTime( demux_t *p_demux, logical_stream_t *p_stream,
797
                                     vlc_tick_t i_time, bool b_fastseek )
798
0
{
799
0
    demux_sys_t *p_sys  = p_demux->p_sys;
800
0
    int64_t i_lowerpos = -1;
801
0
    int64_t i_upperpos = -1;
802
0
    bool b_found = false;
803
804
    /* Search in skeleton */
805
0
    Ogg_GetBoundsUsingSkeletonIndex( p_stream, i_time, &i_lowerpos, &i_upperpos );
806
0
    if ( i_lowerpos != -1 ) b_found = true;
807
808
    /* And also search in our own index */
809
0
    vlc_tick_t foo;
810
0
    if ( !b_found && OggSeekIndexFind( p_stream, i_time, &i_lowerpos, &i_upperpos, &foo ) )
811
0
    {
812
0
        b_found = true;
813
0
    }
814
815
    /* FIXME: add function to get preload time by codec, ex: opus */
816
817
    /* or search */
818
0
    if ( !b_found && b_fastseek )
819
0
    {
820
0
        int64_t i_sync_time;
821
0
        i_lowerpos = OggBisectSearchByTime( p_demux, p_stream, i_time,
822
0
                                            p_stream->i_data_start, p_sys->i_total_bytes,
823
0
                                            &i_sync_time );
824
0
        b_found = ( i_lowerpos != -1 );
825
0
    }
826
827
0
    if ( !b_found ) return -1;
828
829
0
    if ( i_lowerpos < p_stream->i_data_start || i_upperpos > p_sys->i_total_bytes )
830
0
        return -1;
831
832
    /* And really do seek */
833
0
    p_sys->i_input_position = i_lowerpos;
834
0
    seek_byte( p_demux, p_sys->i_input_position );
835
0
    ogg_stream_reset( &p_stream->os );
836
837
0
    return i_lowerpos;
838
0
}
839
840
int Oggseek_BlindSeektoPosition( demux_t *p_demux, logical_stream_t *p_stream,
841
                                 double f, bool b_canfastseek )
842
0
{
843
0
    demux_sys_t *p_sys = p_demux->p_sys;
844
0
    OggDebug( msg_Dbg( p_demux, "=================== Seeking To Blind Pos" ) );
845
0
    int64_t i_size = stream_Size( p_demux->s );
846
0
    uint64_t i_startpos = vlc_stream_Tell( p_demux->s );
847
0
    int64_t i_granule;
848
0
    int64_t i_pagepos;
849
850
0
    i_size = find_first_page_granule( p_demux,
851
0
                                             i_size * f, i_size,
852
0
                                             p_stream,
853
0
                                             &i_granule );
854
0
    if( i_granule == -1 )
855
0
    {
856
0
        if( vlc_stream_Seek( p_demux->s, i_startpos ) != VLC_SUCCESS )
857
0
            msg_Err( p_demux, "Seek back failed. Not seekable ?" );
858
0
        return VLC_EGENERIC;
859
0
    }
860
861
0
    OggDebug( msg_Dbg( p_demux, "Seek start pos is %"PRId64" granule %"PRId64, i_size, i_granule ) );
862
863
0
    i_granule = Ogg_GetKeyframeGranule( p_stream, i_granule );
864
865
0
    if ( b_canfastseek )
866
0
    {
867
        /* Peek back until we meet a keyframe to start our decoding up to our
868
         * final seek time */
869
0
        i_pagepos = OggBackwardSeekToFrame( p_demux,
870
0
                __MAX ( i_size - MAX_PAGE_SIZE, p_stream->i_data_start ),
871
0
                __MIN ( i_size + MAX_PAGE_SIZE, p_sys->i_total_bytes ),
872
0
                p_stream, i_granule );
873
0
    }
874
0
    else
875
0
    {
876
        /* Otherwise, we just sync to the next keyframe we meet */
877
0
        i_pagepos = OggForwardSeekToFrame( p_demux,
878
0
                __MAX ( i_size - MIN_PAGE_SIZE, p_stream->i_data_start ),
879
0
                stream_Size( p_demux->s ),
880
0
                p_stream, i_granule, false );
881
0
    }
882
883
0
    OggDebug( msg_Dbg( p_demux, "=================== Seeked To %"PRId64" granule %"PRId64, i_pagepos, i_granule ) );
884
0
    (void) i_pagepos;
885
0
    return VLC_SUCCESS;
886
0
}
887
888
int Oggseek_SeektoAbsolutetime( demux_t *p_demux, logical_stream_t *p_stream,
889
                                vlc_tick_t i_time )
890
0
{
891
0
    demux_sys_t *p_sys  = p_demux->p_sys;
892
893
0
    OggDebug( msg_Dbg( p_demux, "=================== Seeking To Absolute Time %"PRId64, i_time ) );
894
0
    int64_t i_offset_lower = -1;
895
0
    int64_t i_offset_upper = -1;
896
897
0
    if ( Ogg_GetBoundsUsingSkeletonIndex( p_stream, i_time, &i_offset_lower, &i_offset_upper ) )
898
0
    {
899
        /* Exact match */
900
0
        OggDebug( msg_Dbg( p_demux, "Found keyframe at %"PRId64" using skeleton index", i_offset_lower ) );
901
0
        if ( i_offset_lower == -1 ) i_offset_lower = p_stream->i_data_start;
902
0
        p_sys->i_input_position = i_offset_lower;
903
0
        seek_byte( p_demux, p_sys->i_input_position );
904
0
        ogg_stream_reset( &p_stream->os );
905
0
        return i_offset_lower;
906
0
    }
907
0
    OggDebug( msg_Dbg( p_demux, "Search bounds set to %"PRId64" %"PRId64" using skeleton index", i_offset_lower, i_offset_upper ) );
908
909
910
0
    vlc_tick_t i_lower_index;
911
0
    if(!OggSeekIndexFind( p_stream, i_time, &i_offset_lower, &i_offset_upper, &i_lower_index ))
912
0
        i_lower_index = 0;
913
914
0
    i_offset_lower = __MAX( i_offset_lower, p_stream->i_data_start );
915
0
    i_offset_upper = __MIN( i_offset_upper, p_sys->i_total_bytes );
916
917
0
    int64_t i_sync_time;
918
0
    int64_t i_pagepos = OggBisectSearchByTime( p_demux, p_stream, i_time,
919
0
                                       i_offset_lower, i_offset_upper, &i_sync_time );
920
0
    if ( i_pagepos >= 0 )
921
0
    {
922
        /* be sure to clear any state or read+pagein() will fail on same # */
923
0
        ogg_stream_reset( &p_stream->os );
924
0
        p_sys->i_input_position = i_pagepos;
925
0
        seek_byte( p_demux, p_sys->i_input_position );
926
0
    }
927
928
    /* Insert keyframe position into index */
929
0
    vlc_tick_t index_interval = p_sys->i_length
930
0
              ? vlc_tick_from_sec( ceil( sqrt( SEC_FROM_VLC_TICK( p_sys->i_length ) ) / 2 ) )
931
0
              : vlc_tick_from_sec( 5 );
932
0
    if ( i_pagepos >= p_stream->i_data_start && ( i_sync_time - i_lower_index >= index_interval ) )
933
0
        OggSeek_IndexAdd( p_stream, i_sync_time, i_pagepos );
934
935
0
    OggDebug( msg_Dbg( p_demux, "=================== Seeked To %"PRId64" time %"PRId64, i_pagepos, i_time ) );
936
0
    return i_pagepos;
937
0
}
938
939
/****************************************************************************
940
 * oggseek_read_page: Read a full Ogg page from the physical bitstream.
941
 ****************************************************************************
942
 * Returns number of bytes read. This should always be > 0
943
 * unless we are at the end of stream.
944
 *
945
 ****************************************************************************/
946
947
int64_t oggseek_read_page( demux_t *p_demux )
948
0
{
949
0
    demux_sys_t *p_ogg = p_demux->p_sys  ;
950
0
    uint8_t header[PAGE_HEADER_BYTES+255];
951
0
    int i_nsegs;
952
0
    int i;
953
0
    int64_t i_in_pos;
954
0
    int64_t i_result;
955
0
    int i_page_size;
956
0
    char *buf;
957
958
0
    demux_sys_t *p_sys  = p_demux->p_sys;
959
960
    /* store position of this page */
961
0
    i_in_pos = p_ogg->i_input_position = vlc_stream_Tell( p_demux->s );
962
963
0
    if ( p_sys->b_page_waiting) {
964
0
        msg_Warn( p_demux, "Ogg page already loaded" );
965
0
        return 0;
966
0
    }
967
968
0
    if ( vlc_stream_Read ( p_demux->s, header, PAGE_HEADER_BYTES ) < PAGE_HEADER_BYTES )
969
0
    {
970
0
        vlc_stream_Seek( p_demux->s, i_in_pos );
971
0
        msg_Dbg ( p_demux, "Reached clean EOF in ogg file" );
972
0
        return 0;
973
0
    }
974
975
0
    i_nsegs = header[ PAGE_HEADER_BYTES - 1 ];
976
977
0
    if ( vlc_stream_Read ( p_demux->s, header+PAGE_HEADER_BYTES, i_nsegs ) < i_nsegs )
978
0
    {
979
0
        vlc_stream_Seek( p_demux->s, i_in_pos );
980
0
        msg_Warn ( p_demux, "Reached broken EOF in ogg file" );
981
0
        return 0;
982
0
    }
983
984
0
    i_page_size = PAGE_HEADER_BYTES + i_nsegs;
985
986
0
    for ( i = 0; i < i_nsegs; i++ )
987
0
    {
988
0
        i_page_size += header[ PAGE_HEADER_BYTES + i ];
989
0
    }
990
991
0
    ogg_sync_reset( &p_ogg->oy );
992
993
0
    buf = ogg_sync_buffer( &p_ogg->oy, i_page_size );
994
0
    if( !buf )
995
0
        return 0;
996
997
0
    memcpy( buf, header, PAGE_HEADER_BYTES + i_nsegs );
998
999
0
    i_result = vlc_stream_Read ( p_demux->s, (uint8_t*)buf + PAGE_HEADER_BYTES + i_nsegs,
1000
0
                             i_page_size - PAGE_HEADER_BYTES - i_nsegs );
1001
1002
0
    ogg_sync_wrote( &p_ogg->oy, i_result + PAGE_HEADER_BYTES + i_nsegs );
1003
1004
1005
1006
1007
0
    if ( ogg_sync_pageout( &p_ogg->oy, &p_ogg->current_page ) != 1 )
1008
0
    {
1009
0
        msg_Err( p_demux , "Got invalid packet, read %"PRId64" of %i: %s %"PRId64,
1010
0
                 i_result, i_page_size, buf, i_in_pos );
1011
0
        return 0;
1012
0
    }
1013
1014
0
    return i_result + PAGE_HEADER_BYTES + i_nsegs;
1015
0
}
1016