Coverage Report

Created: 2026-09-28 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/demux/nuv.c
Line
Count
Source
1
/*****************************************************************************
2
 * nuv.c:
3
 *****************************************************************************
4
 * Copyright (C) 2005 VLC authors and VideoLAN
5
 *
6
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
7
 *          Gertjan Van Droogenbroeck <gertjanvd _PLUS_ vlc _AT_ gmail _DOT_ com>
8
 *
9
 * This program is free software; you can redistribute it and/or modify it
10
 * under the terms of the GNU Lesser General Public License as published by
11
 * the Free Software Foundation; either version 2.1 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with this program; if not, write to the Free Software Foundation,
21
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
 *****************************************************************************/
23
24
/*****************************************************************************
25
 * Preamble
26
 *****************************************************************************/
27
#ifdef HAVE_CONFIG_H
28
# include "config.h"
29
#endif
30
31
#include <math.h>
32
33
#include <vlc_common.h>
34
#include <vlc_plugin.h>
35
#include <vlc_demux.h>
36
#include <limits.h>
37
#include <stdckdint.h>
38
39
/* TODO:
40
 *  - test
41
 */
42
43
/*****************************************************************************
44
 * Module descriptor
45
 *****************************************************************************/
46
static int  Open  ( vlc_object_t * );
47
static void Close ( vlc_object_t * );
48
49
174
vlc_module_begin ()
50
87
    set_subcategory( SUBCAT_INPUT_DEMUX )
51
87
    set_description( N_("Nuv demuxer") )
52
87
    set_capability( "demux", 145 )
53
174
    set_callbacks( Open, Close )
54
87
    add_shortcut( "nuv" )
55
87
vlc_module_end ()
56
57
/*****************************************************************************
58
 * Local prototypes
59
 *****************************************************************************/
60
static int Demux  ( demux_t * );
61
static int Control( demux_t *, int, va_list );
62
63
/* */
64
typedef struct
65
{
66
    vlc_tick_t i_time;
67
    uint64_t i_offset;
68
} demux_index_entry_t;
69
70
typedef struct
71
{
72
    int i_idx;
73
    int i_idx_max;
74
75
    demux_index_entry_t *idx;
76
} demux_index_t;
77
78
79
static void demux_IndexInit( demux_index_t * );
80
static void demux_IndexClean( demux_index_t * );
81
static void demux_IndexAppend( demux_index_t *,
82
                               int64_t i_time, int64_t i_offset );
83
/* Convert a time into offset */
84
static uint64_t demux_IndexConvertTime(demux_index_t *, vlc_tick_t time);
85
/* Find the nearest offset in the index */
86
static uint64_t demux_IndexFindOffset(demux_index_t *, uint64_t offset);
87
88
89
/* */
90
typedef struct
91
{
92
    char id[12];       /* "NuppelVideo\0" or "MythTVVideo\0" */
93
    char version[5];    /* "x.xx\0" */
94
95
    int  i_width;
96
    int  i_height;
97
    int  i_width_desired;
98
    int  i_height_desired;
99
100
    char i_mode;            /* P progressive, I interlaced */
101
102
    double  d_aspect;       /* 1.0 squared pixel */
103
    double  d_fps;
104
105
    int     i_video_blocks; /* 0 no video, -1 unknown */
106
    int     i_audio_blocks;
107
    int     i_text_blocks;
108
109
    int     i_keyframe_distance;
110
111
} header_t;
112
113
911
#define NUV_FH_SIZE 12
114
574
#define NUV_TIMECODE_BASE CLOCK_FREQ
115
typedef struct
116
{
117
    char i_type;        /* A: audio, V: video, S: sync; T: test
118
                           R: Seekpoint (string:RTjjjjjjjj)
119
                           D: Extra data for codec
120
                           X: extended data Q: seektable */
121
    char i_compression; /* V: 0 uncompressed
122
                              1 RTJpeg
123
                              2 RTJpeg+lzo
124
                              N black frame
125
                              L copy last
126
                           A: 0 uncompressed (44100 1-bits, 2ch)
127
                              1 lzo
128
                              2 layer 2
129
                              3 layer 3
130
                              F flac
131
                              S shorten
132
                              N null frame loudless
133
                              L copy last
134
                            S: B audio and vdeo sync point
135
                               A audio sync info (timecode == effective
136
                                    dsp frequency*100)
137
                               V next video sync (timecode == next video
138
                                    frame num)
139
                               S audio,video,text correlation */
140
    char i_keyframe;    /* 0 keyframe, else no no key frame */
141
    uint8_t i_filters;  /* 0x01: gauss 5 pixel (8,2,2,2,2)/16
142
                           0x02: gauss 5 pixel (8,1,1,1,1)/12
143
                           0x04: cartoon filter */
144
145
    int i_timecode;     /* ms */
146
147
    int i_length;       /* V,A,T: length of following data
148
                           S: length of packet correl */
149
} frame_header_t;
150
151
typedef struct
152
{
153
    int             i_version;
154
    vlc_fourcc_t    i_video_fcc;
155
156
    vlc_fourcc_t    i_audio_fcc;
157
    int             i_audio_sample_rate;
158
    int             i_audio_bits_per_sample;
159
    int             i_audio_channels;
160
    int             i_audio_compression_ratio;
161
    int             i_audio_quality;
162
    int             i_rtjpeg_quality;
163
    int             i_rtjpeg_luma_filter;
164
    int             i_rtjpeg_chroma_filter;
165
    int             i_lavc_bitrate;
166
    int             i_lavc_qmin;
167
    int             i_lavc_qmax;
168
    int             i_lavc_maxqdiff;
169
    int64_t         i_seektable_offset;
170
    int64_t         i_keyframe_adjust_offset;
171
172
} extended_header_t;
173
174
typedef struct
175
{
176
    header_t          hdr;
177
    extended_header_t exh;
178
179
    vlc_tick_t  i_pcr;
180
    es_out_id_t *p_es_video;
181
    int         i_extra_f;
182
    uint8_t     *p_extra_f;
183
184
    es_out_id_t *p_es_audio;
185
186
    /* index */
187
    demux_index_t idx;
188
    bool b_index;
189
    bool b_seekable;
190
    /* frameheader buffer */
191
    uint8_t fh_buffer[NUV_FH_SIZE];
192
    vlc_tick_t i_total_length;
193
    /* first frame position (used for calculating size without seektable) */
194
    uint64_t i_first_frame_offset;
195
} demux_sys_t;
196
197
static int HeaderLoad( demux_t *, header_t *h );
198
static int FrameHeaderLoad( demux_t *, frame_header_t *h );
199
static int ExtendedHeaderLoad( demux_t *, extended_header_t *h );
200
static int SeekTableLoad( demux_t *, demux_sys_t * );
201
static int ControlSetPosition(demux_t *demux, uint64_t offset, bool guess);
202
203
/*****************************************************************************
204
 * Open: initializes ES structures
205
 *****************************************************************************/
206
static int Open( vlc_object_t * p_this )
207
380
{
208
380
    demux_t     *p_demux = (demux_t*)p_this;
209
380
    demux_sys_t *p_sys;
210
380
    const uint8_t *p_peek;
211
380
    frame_header_t fh;
212
213
    /* Check id */
214
380
    if( vlc_stream_Peek( p_demux->s, &p_peek, 12 ) != 12 ||
215
380
        ( strncmp( (char *)p_peek, "MythTVVideo", 11 ) &&
216
290
          strncmp( (char *)p_peek, "NuppelVideo", 11 ) ) )
217
23
        return VLC_EGENERIC;
218
219
357
    p_sys = malloc( sizeof( demux_sys_t ) );
220
357
    if( p_sys == NULL )
221
0
        return VLC_ENOMEM;
222
357
    memset( p_sys, 0, sizeof( demux_sys_t ) );
223
357
    p_sys->p_es_video = NULL;
224
357
    p_sys->p_es_audio = NULL;
225
357
    p_sys->p_extra_f = NULL;
226
357
    p_sys->i_pcr = -1;
227
357
    p_sys->b_index = false;
228
357
    p_sys->i_total_length = -1;
229
357
    demux_IndexInit( &p_sys->idx );
230
231
357
    p_demux->p_sys = p_sys;
232
233
    /* Info about the stream */
234
357
    vlc_stream_Control( p_demux->s, STREAM_CAN_SEEK, &p_sys->b_seekable );
235
#if 0
236
    if( p_sys->b_seekable )
237
        msg_Dbg( p_demux, "stream is seekable" );
238
    else
239
        msg_Dbg( p_demux, "stream is NOT seekable" );
240
#endif
241
242
357
    if( HeaderLoad( p_demux, &p_sys->hdr ) )
243
96
        goto error;
244
245
    /* Load 'D' */
246
261
    if( FrameHeaderLoad( p_demux, &fh ) || fh.i_type != 'D' )
247
0
        goto error;
248
261
    if( fh.i_length > 0 )
249
261
    {
250
261
        if( fh.i_compression == 'F' || fh.i_compression == 'R' )
251
236
        {
252
            /* libavcodec extra data */
253
236
            p_sys->i_extra_f = fh.i_length;
254
236
            p_sys->p_extra_f = malloc( fh.i_length );
255
236
            if( p_sys->p_extra_f == NULL || vlc_stream_Read( p_demux->s,
256
236
                             p_sys->p_extra_f, fh.i_length ) != fh.i_length )
257
0
                goto error;
258
236
        }
259
25
        else
260
25
        {
261
25
            msg_Warn( p_demux, "unsupported 'D' frame (c=%c)", fh.i_compression );
262
25
            if( vlc_stream_Read( p_demux->s, NULL, fh.i_length ) != fh.i_length )
263
0
                goto error;
264
25
        }
265
261
    }
266
267
    /* Check and load extended */
268
261
    if( vlc_stream_Peek( p_demux->s, &p_peek, 1 ) != 1 )
269
0
        goto error;
270
261
    if( p_peek[0] == 'X' )
271
261
    {
272
261
        if( FrameHeaderLoad( p_demux, &fh ) )
273
0
            goto error;
274
261
        if( fh.i_length != 512 )
275
0
            goto error;
276
277
261
        if( ExtendedHeaderLoad( p_demux, &p_sys->exh ) )
278
0
            goto error;
279
280
261
        if( !p_sys->b_seekable )
281
261
            msg_Warn( p_demux, "stream is not seekable, skipping seektable" );
282
261
        else if( SeekTableLoad( p_demux, p_sys ) )
283
77
        {
284
77
            p_sys->b_index = false;
285
77
            msg_Warn( p_demux, "Seektable is broken, seek won't be accurate" );
286
77
        }
287
261
    }
288
0
    else
289
0
    {
290
        /* XXX: for now only file with extended chunk are supported
291
         * why: because else we need to have support for rtjpeg+stupid nuv shit */
292
0
        msg_Err( p_demux, "VLC doesn't support NUV without extended chunks (please upload samples)" );
293
0
        goto error;
294
0
    }
295
296
    /* Create audio/video (will work only with extended header and audio=mp3 */
297
261
    int id = 0;
298
261
    if( p_sys->hdr.i_video_blocks != 0 )
299
261
    {
300
261
        es_format_t fmt;
301
302
261
        es_format_Init( &fmt, VIDEO_ES, p_sys->exh.i_video_fcc );
303
261
        fmt.video.i_width = p_sys->hdr.i_width;
304
261
        fmt.video.i_height = p_sys->hdr.i_height;
305
261
        fmt.video.i_visible_width = fmt.video.i_width;
306
261
        fmt.video.i_visible_height = fmt.video.i_height;
307
261
        fmt.i_extra = p_sys->i_extra_f;
308
261
        fmt.p_extra = p_sys->p_extra_f;
309
261
        fmt.video.i_sar_num = p_sys->hdr.d_aspect * fmt.video.i_height;
310
261
        fmt.video.i_sar_den = fmt.video.i_width;
311
312
261
        fmt.i_id = id++;
313
261
        p_sys->p_es_video = es_out_Add( p_demux->out, &fmt );
314
261
    }
315
261
    if( p_sys->hdr.i_audio_blocks != 0 )
316
261
    {
317
261
        es_format_t fmt;
318
319
261
        es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_MPGA );
320
261
        fmt.audio.i_rate = p_sys->exh.i_audio_sample_rate;
321
261
        fmt.audio.i_bitspersample = p_sys->exh.i_audio_bits_per_sample;
322
323
261
        fmt.i_id = id++;
324
261
        p_sys->p_es_audio = es_out_Add( p_demux->out, &fmt );
325
261
    }
326
261
    if( p_sys->hdr.i_text_blocks != 0 )
327
7
    {
328
7
        msg_Warn( p_demux, "text not yet supported (upload samples)" );
329
7
    }
330
331
261
    p_sys->i_first_frame_offset = vlc_stream_Tell( p_demux->s );
332
333
    /* Fill p_demux fields */
334
261
    p_demux->pf_demux = Demux;
335
261
    p_demux->pf_control = Control;
336
337
261
    return VLC_SUCCESS;
338
339
96
error:
340
96
    msg_Warn( p_demux, "cannot load Nuv file" );
341
96
    Close( p_this );
342
96
    p_demux->p_sys = NULL;
343
96
    return VLC_EGENERIC;
344
261
}
345
346
/*****************************************************************************
347
 * Close: frees unused data
348
 *****************************************************************************/
349
static void Close( vlc_object_t * p_this )
350
357
{
351
357
    demux_t        *p_demux = (demux_t*)p_this;
352
357
    demux_sys_t    *p_sys = p_demux->p_sys;
353
354
357
    free( p_sys->p_extra_f );
355
357
    demux_IndexClean( &p_sys->idx );
356
357
    free( p_sys );
357
357
}
358
359
/*****************************************************************************
360
 * Demux: reads and demuxes data packets
361
 *****************************************************************************
362
 * Returns -1 in case of error, 0 in case of EOF, 1 otherwise
363
 *****************************************************************************/
364
static int Demux( demux_t *p_demux )
365
835
{
366
835
    demux_sys_t *p_sys = p_demux->p_sys;
367
835
    frame_header_t fh;
368
835
    block_t *p_data;
369
370
835
    for( ;; )
371
7.15k
    {
372
7.15k
        if( FrameHeaderLoad( p_demux, &fh ) )
373
181
            return VLC_DEMUXER_EOF;
374
375
6.97k
        if( fh.i_type == 'A' || fh.i_type == 'V' )
376
574
            break;
377
378
        /* TODO add support for some block type */
379
380
6.40k
        if( fh.i_type != 'R' && fh.i_length > 0 )
381
323
        {
382
323
            if( vlc_stream_Read( p_demux->s, NULL, fh.i_length ) != fh.i_length )
383
80
                return VLC_DEMUXER_EGENERIC;
384
323
        }
385
6.40k
    }
386
387
    /* */
388
574
    if( ( p_data = vlc_stream_Block( p_demux->s, fh.i_length ) ) == NULL )
389
0
        return VLC_DEMUXER_EOF;
390
574
    p_data->i_dts = VLC_TICK_0 + NUV_TIMECODE_BASE + (int64_t)fh.i_timecode * 1000;
391
574
    p_data->i_pts = (fh.i_type == 'V') ? VLC_TICK_INVALID : p_data->i_dts;
392
393
    /* only add keyframes to index */
394
574
    if( !fh.i_keyframe && !p_sys->b_index )
395
515
        demux_IndexAppend( &p_sys->idx,
396
515
                           (int64_t)fh.i_timecode * 1000,
397
515
                           vlc_stream_Tell(p_demux->s) - NUV_FH_SIZE );
398
399
    /* */
400
574
    if( p_sys->i_pcr < 0 || p_sys->i_pcr < p_data->i_dts - VLC_TICK_0 )
401
193
    {
402
193
        p_sys->i_pcr = p_data->i_dts - VLC_TICK_0;
403
193
        es_out_SetPCR( p_demux->out, VLC_TICK_0 + p_sys->i_pcr );
404
193
    }
405
406
574
    if( fh.i_type == 'A' && p_sys->p_es_audio )
407
63
    {
408
63
        if( fh.i_compression == '3' )
409
60
            es_out_Send( p_demux->out, p_sys->p_es_audio, p_data );
410
3
        else
411
3
        {
412
3
            msg_Dbg( p_demux, "unsupported compression %c for audio (upload samples)", fh.i_compression );
413
3
            block_Release( p_data );
414
3
        }
415
63
    }
416
511
    else if( fh.i_type == 'V' && p_sys->p_es_video )
417
511
    {
418
511
        if( fh.i_compression >='0' && fh.i_compression <='3' )
419
198
        {
420
            /* for rtjpeg data, the header is also needed */
421
198
            p_data = block_Realloc( p_data, NUV_FH_SIZE, fh.i_length );
422
198
            if( unlikely(!p_data) )
423
0
                return VLC_DEMUXER_EGENERIC;
424
198
            memcpy( p_data->p_buffer, p_sys->fh_buffer, NUV_FH_SIZE );
425
198
        }
426
        /* 0,1,2,3 -> rtjpeg, >=4 mpeg4 */
427
511
        if( fh.i_compression >= '0' )
428
511
            es_out_Send( p_demux->out, p_sys->p_es_video, p_data );
429
0
        else
430
0
        {
431
0
            msg_Dbg( p_demux, "unsupported compression %c for video (upload samples)", fh.i_compression );
432
0
            block_Release( p_data );
433
0
        }
434
511
    }
435
0
    else
436
0
    {
437
0
        block_Release( p_data );
438
0
    }
439
440
574
    return VLC_DEMUXER_SUCCESS;
441
574
}
442
443
/*****************************************************************************
444
 * Control:
445
 *****************************************************************************/
446
static int Control( demux_t *p_demux, int i_query, va_list args )
447
0
{
448
0
    demux_sys_t *p_sys  = p_demux->p_sys;
449
0
    double *pf;
450
0
    int64_t i64;
451
452
0
    switch( i_query )
453
0
    {
454
0
        case DEMUX_CAN_SEEK:
455
0
            *va_arg( args, bool * ) = p_sys->b_seekable;
456
0
            return VLC_SUCCESS;
457
458
0
        case DEMUX_GET_POSITION:
459
0
            pf = va_arg( args, double * );
460
461
0
            if( p_sys->i_total_length > 0 && p_sys->i_pcr >= 0 )
462
0
            {
463
0
                vlc_tick_t time = __MAX(p_sys->i_pcr - NUV_TIMECODE_BASE, 0);
464
0
                *pf = __MIN((double)time / p_sys->i_total_length, 1.0);
465
0
            }
466
0
            else
467
0
            {
468
0
                i64 = stream_Size( p_demux->s );
469
0
                if( i64 > 0 )
470
0
                {
471
0
                    const double f_current = vlc_stream_Tell( p_demux->s );
472
0
                    *pf = f_current / (double)i64;
473
0
                }
474
0
                else
475
0
                {
476
0
                    *pf = 0.0;
477
0
                }
478
0
            }
479
0
            return VLC_SUCCESS;
480
481
0
        case DEMUX_SET_POSITION:
482
0
        {
483
0
            double f = va_arg(args, double);
484
0
            uint64_t offset;
485
486
0
            p_sys->i_pcr = -1;
487
488
            /* first try to see if we can seek based on time (== GET_LENGTH works) */
489
0
            if (p_sys->i_total_length > 0) {
490
0
                vlc_tick_t t = llround(p_sys->i_total_length * f);
491
492
0
                offset = demux_IndexConvertTime(&p_sys->idx, t);
493
0
                if (offset != UINT64_C(-1))
494
0
                    return ControlSetPosition(p_demux, offset, false);
495
0
            }
496
497
            /* if not search based on total stream size */
498
0
            offset = stream_Size(p_demux->s) * f;
499
0
            offset = demux_IndexFindOffset(&p_sys->idx, offset);
500
0
            if (offset != UINT64_C(-1))
501
0
                return ControlSetPosition(p_demux, offset, false);
502
503
0
            offset = p_sys->i_first_frame_offset
504
0
                     + (uint64_t)((stream_Size(p_demux->s)
505
0
                                   - p_sys->i_first_frame_offset) * f);
506
0
            return ControlSetPosition(p_demux, offset, true);
507
0
        }
508
509
0
        case DEMUX_GET_NORMAL_TIME:
510
0
            *va_arg( args, vlc_tick_t * ) = VLC_TICK_0 + NUV_TIMECODE_BASE;
511
0
            return VLC_SUCCESS;
512
513
0
        case DEMUX_GET_TIME:
514
0
            *va_arg( args, vlc_tick_t * ) = __MAX(p_sys->i_pcr - NUV_TIMECODE_BASE, 0);
515
0
            return VLC_SUCCESS;
516
517
0
        case DEMUX_SET_TIME:
518
0
        {
519
0
            uint64_t i_pos;
520
521
0
            p_sys->i_pcr = -1;
522
523
0
            i_pos = demux_IndexConvertTime( &p_sys->idx, va_arg( args, vlc_tick_t ) );
524
0
            if (i_pos != UINT64_C(-1))
525
0
                return ControlSetPosition( p_demux, i_pos, false );
526
0
            return VLC_EGENERIC;
527
0
        }
528
529
0
        case DEMUX_GET_LENGTH:
530
0
            if( p_sys->i_total_length >= 0 )
531
0
            {
532
0
                *va_arg( args, vlc_tick_t * ) = p_sys->i_total_length;
533
0
                return VLC_SUCCESS;
534
0
            }
535
0
            else if( vlc_stream_Tell( p_demux->s ) > p_sys->i_first_frame_offset )
536
0
            {
537
                /* This should give an approximation of the total duration */
538
0
                if (p_sys->i_pcr <= NUV_TIMECODE_BASE)
539
0
                    *va_arg( args, vlc_tick_t * ) = 0;
540
0
                else
541
0
                    *va_arg( args, vlc_tick_t * ) = (p_sys->i_pcr - NUV_TIMECODE_BASE) *
542
0
                            (double)( stream_Size( p_demux->s ) - p_sys->i_first_frame_offset ) /
543
0
                            (double)( vlc_stream_Tell( p_demux->s ) - p_sys->i_first_frame_offset );
544
545
0
                return VLC_SUCCESS;
546
0
            }
547
0
            else
548
0
                return VLC_EGENERIC;
549
550
0
        case DEMUX_GET_FPS:
551
0
            pf = va_arg( args, double * );
552
0
            *pf = p_sys->hdr.d_fps;
553
0
            return VLC_SUCCESS;
554
555
0
        case DEMUX_GET_META:
556
0
            return VLC_EGENERIC;
557
558
0
        case DEMUX_CAN_PAUSE:
559
0
        case DEMUX_SET_PAUSE_STATE:
560
0
        case DEMUX_CAN_CONTROL_PACE:
561
0
        case DEMUX_GET_PTS_DELAY:
562
0
            return demux_vaControlHelper( p_demux->s, 0, -1, 0, 1, i_query, args );
563
564
0
        default:
565
0
            return VLC_EGENERIC;
566
567
0
    }
568
0
}
569
570
static int ControlSetPosition(demux_t *p_demux, uint64_t offset, bool b_guess)
571
0
{
572
0
    demux_sys_t *p_sys  = p_demux->p_sys;
573
574
    /* if we can seek in the stream */
575
0
    if( p_sys->b_seekable && !b_guess )
576
0
    {
577
0
        if (vlc_stream_Seek(p_demux->s, offset))
578
0
            return VLC_EGENERIC;
579
0
    }
580
0
    else
581
0
    {
582
        /* forward seek */
583
0
        if (offset > vlc_stream_Tell(p_demux->s))
584
0
        {
585
0
            msg_Dbg( p_demux, "unable to seek, skipping frames (slow)" );
586
0
        }
587
0
        else
588
0
        {
589
0
            msg_Warn( p_demux, "unable to seek, only forward seeking is possible" );
590
591
0
            return VLC_EGENERIC;
592
0
        }
593
0
    }
594
595
0
    for( ;; )
596
0
    {
597
0
        frame_header_t fh;
598
0
        uint64_t i_tell;
599
600
0
        if ((i_tell = vlc_stream_Tell(p_demux->s)) >= offset)
601
0
            break;
602
603
0
        if( FrameHeaderLoad( p_demux, &fh ) )
604
0
            return VLC_EGENERIC;
605
606
0
        if( fh.i_type == 'A' || fh.i_type == 'V' )
607
0
        {
608
0
            if( !fh.i_keyframe && !p_sys->b_index )
609
0
                demux_IndexAppend( &p_sys->idx,(int64_t)fh.i_timecode*1000, i_tell );
610
0
        }
611
612
0
        if( fh.i_type != 'R' && fh.i_length > 0 )
613
0
        {
614
0
            if( vlc_stream_Read( p_demux->s, NULL, fh.i_length ) != fh.i_length )
615
0
                return VLC_EGENERIC;
616
0
        }
617
0
    }
618
619
0
    return VLC_SUCCESS;
620
0
}
621
622
/*****************************************************************************
623
 *
624
 *****************************************************************************/
625
static inline void GetDoubleLE( double *pd, void *src )
626
714
{
627
    /* FIXME works only if sizeof(double) == 8 */
628
#ifdef WORDS_BIGENDIAN
629
    uint8_t *p = (uint8_t*)pd, *q = (uint8_t*)src;
630
    int i;
631
    for( i = 0; i < 8; i++ )
632
        p[i] = q[7-i];
633
#else
634
714
    memcpy( pd, src, 8 );
635
714
#endif
636
714
}
637
638
static bool FrameToTime( int64_t frame, double fps, vlc_tick_t *time )
639
0
{
640
0
    double value = (double)frame * (double)CLOCK_FREQ / fps;
641
642
0
    if( !isfinite(value) || value < 0.0 || value >= (double)VLC_TICK_MAX ||
643
0
        ( frame > 0 && value < 1.0 ) )
644
0
        return false;
645
646
0
    *time = (vlc_tick_t)value;
647
0
    return true;
648
0
}
649
650
/* HeaderLoad:
651
 */
652
static int HeaderLoad( demux_t *p_demux, header_t *h )
653
357
{
654
357
    uint8_t buffer[72];
655
656
357
    if( vlc_stream_Read( p_demux->s, buffer, 72 ) != 72 )
657
0
        return VLC_EGENERIC;
658
659
    /* XXX: they are alignment to take care of (another broken format) */
660
357
    memcpy( h->id,      &buffer[ 0], 12 );
661
357
    memcpy( h->version, &buffer[12], 5 );
662
357
    h->i_width = GetDWLE( &buffer[20] );
663
357
    h->i_height = GetDWLE( &buffer[24] );
664
357
    h->i_width_desired = GetDWLE( &buffer[28] );
665
357
    h->i_height_desired = GetDWLE( &buffer[32] );
666
357
    h->i_mode = buffer[36];
667
357
    GetDoubleLE( &h->d_aspect, &buffer[40] );
668
357
    GetDoubleLE( &h->d_fps, &buffer[48] );
669
357
    h->i_video_blocks = GetDWLE( &buffer[56] );
670
357
    h->i_audio_blocks = GetDWLE( &buffer[60] );
671
357
    h->i_text_blocks = GetDWLE( &buffer[64] );
672
357
    h->i_keyframe_distance = GetDWLE( &buffer[68] );
673
#if 0
674
    msg_Dbg( p_demux, "nuv: h=%s v=%s %dx%d a=%f fps=%f v=%d a=%d t=%d kfd=%d",
675
             h->id, h->version, h->i_width, h->i_height, h->d_aspect,
676
             h->d_fps, h->i_video_blocks, h->i_audio_blocks, h->i_text_blocks,
677
             h->i_keyframe_distance );
678
#endif
679
680
357
    if( !isfinite(h->d_fps) || h->d_fps <= 0.0 ||
681
356
        h->i_video_blocks < -1 || h->i_audio_blocks < -1 || h->i_text_blocks < -1 ||
682
285
        h->i_keyframe_distance < 0 )
683
72
        return VLC_EGENERIC;
684
685
285
    if( h->i_video_blocks != 0 )
686
285
    {
687
285
        if( h->i_width <= 0 || h->i_height <= 0 ||
688
277
            !isfinite(h->d_aspect) || h->d_aspect <= 0.0 ||
689
274
            h->d_aspect >= (double)UINT_MAX / h->i_height )
690
24
            return VLC_EGENERIC;
691
285
    }
692
693
261
    return VLC_SUCCESS;
694
285
}
695
696
/* FrameHeaderLoad:
697
 */
698
static int FrameHeaderLoad( demux_t *p_demux, frame_header_t *h )
699
7.75k
{
700
7.75k
    demux_sys_t *p_sys = p_demux->p_sys;
701
7.75k
    uint8_t* buffer = p_sys->fh_buffer;
702
703
7.75k
    if( vlc_stream_Read( p_demux->s, buffer, 12 ) != 12 )
704
181
        return VLC_EGENERIC;
705
706
7.57k
    h->i_type = buffer[0];
707
7.57k
    h->i_compression = buffer[1];
708
7.57k
    h->i_keyframe = buffer[2];
709
7.57k
    h->i_filters = buffer[3];
710
711
7.57k
    h->i_timecode = GetDWLE( &buffer[4] );
712
7.57k
    h->i_length = GetDWLE( &buffer[8] );
713
#if 0
714
    msg_Dbg( p_demux, "frame hdr: t=%c c=%c k=%d f=0x%x timecode=%d l=%d",
715
             h->i_type,
716
             h->i_compression ? h->i_compression : ' ',
717
             h->i_keyframe ? h->i_keyframe : ' ',
718
             h->i_filters,
719
             h->i_timecode, h->i_length );
720
#endif
721
7.57k
    return VLC_SUCCESS;
722
7.75k
}
723
724
static int ExtendedHeaderLoad( demux_t *p_demux, extended_header_t *h )
725
261
{
726
261
    uint8_t buffer[512];
727
728
261
    if( vlc_stream_Read( p_demux->s, buffer, 512 ) != 512 )
729
0
        return VLC_EGENERIC;
730
731
261
    h->i_version = GetDWLE( &buffer[0] );
732
261
    h->i_video_fcc = VLC_FOURCC( buffer[4], buffer[5], buffer[6], buffer[7] );
733
261
    h->i_audio_fcc = VLC_FOURCC( buffer[8], buffer[9], buffer[10], buffer[11] );
734
261
    h->i_audio_sample_rate = GetDWLE( &buffer[12] );
735
261
    h->i_audio_bits_per_sample = GetDWLE( &buffer[16] );
736
261
    h->i_audio_channels = GetDWLE( &buffer[20] );
737
261
    h->i_audio_compression_ratio = GetDWLE( &buffer[24] );
738
261
    h->i_audio_quality = GetDWLE( &buffer[28] );
739
261
    h->i_rtjpeg_quality = GetDWLE( &buffer[32] );
740
261
    h->i_rtjpeg_luma_filter = GetDWLE( &buffer[36] );
741
261
    h->i_rtjpeg_chroma_filter = GetDWLE( &buffer[40] );
742
261
    h->i_lavc_bitrate = GetDWLE( &buffer[44] );
743
261
    h->i_lavc_qmin = GetDWLE( &buffer[48] );
744
261
    h->i_lavc_qmin = GetDWLE( &buffer[52] );
745
261
    h->i_lavc_maxqdiff = GetDWLE( &buffer[56] );
746
261
    h->i_seektable_offset = GetQWLE( &buffer[60] );
747
261
    h->i_keyframe_adjust_offset= GetQWLE( &buffer[68] );
748
#if 0
749
    msg_Dbg( p_demux, "ex hdr: v=%d vffc=%4.4s afcc=%4.4s %dHz %dbits ach=%d acr=%d aq=%d"
750
                      "rtjpeg q=%d lf=%d lc=%d lavc br=%d qmin=%d qmax=%d maxqdiff=%d seekableoff=%"PRIi64" keyfao=%"PRIi64,
751
             h->i_version,
752
             (char*)&h->i_video_fcc,
753
             (char*)&h->i_audio_fcc, h->i_audio_sample_rate, h->i_audio_bits_per_sample, h->i_audio_channels,
754
             h->i_audio_compression_ratio, h->i_audio_quality,
755
             h->i_rtjpeg_quality, h->i_rtjpeg_luma_filter, h->i_rtjpeg_chroma_filter,
756
             h->i_lavc_bitrate, h->i_lavc_qmin, h->i_lavc_qmax, h->i_lavc_maxqdiff,
757
             h->i_seektable_offset, h->i_keyframe_adjust_offset );
758
#endif
759
261
    return VLC_SUCCESS;
760
261
}
761
762
/*
763
    typedef struct
764
    {
765
      int64_t i_file_offset;
766
      int32_t i_keyframe_number;
767
    } seektable_entry_t;
768
    typedef struct
769
    {
770
       int32_t i_adjust;
771
       int32_t i_keyframe_number;
772
    } kfatable_entry_t;
773
*/
774
775
static int SeekTableLoad( demux_t *p_demux, demux_sys_t *p_sys )
776
261
{
777
261
    frame_header_t fh;
778
261
    uint64_t i_original_pos;
779
261
    int32_t last_keyframe = 0, frame = 0, kfa_entry_id = 0;
780
261
    int ret = VLC_EGENERIC;
781
782
261
    if( p_sys->exh.i_seektable_offset <= 0 )
783
184
        return VLC_SUCCESS;
784
785
77
    i_original_pos = vlc_stream_Tell( p_demux->s ); /* Save current position */
786
#if 0
787
    msg_Dbg( p_demux, "current offset %"PRIu64, i_original_pos );
788
789
    msg_Dbg( p_demux, "seeking in stream to %"PRIi64, p_sys->exh.i_seektable_offset );
790
#endif
791
77
    if( vlc_stream_Seek( p_demux->s, p_sys->exh.i_seektable_offset ) )
792
0
        goto restore;
793
794
77
    if( FrameHeaderLoad( p_demux, &fh ) )
795
0
        goto restore;
796
797
77
    if( fh.i_type != 'Q' || fh.i_length < 12 )
798
77
    {
799
77
        msg_Warn( p_demux, "invalid seektable, frame type=%c", fh.i_type );
800
77
        goto restore;
801
77
    }
802
803
    /* */
804
0
    uint8_t *p_seek_table = malloc( fh.i_length );
805
0
    if( p_seek_table == NULL )
806
0
    {
807
0
        ret = VLC_ENOMEM;
808
0
        goto restore;
809
0
    }
810
811
0
    if( vlc_stream_Read( p_demux->s, p_seek_table,
812
0
                         fh.i_length ) != fh.i_length )
813
0
    {
814
0
        free( p_seek_table );
815
0
        goto restore;
816
0
    }
817
0
    const int32_t i_seek_elements = fh.i_length / 12;
818
819
    /* Get keyframe adjust offsets */
820
    /*  typedef struct kfatable_entry
821
        {
822
            int adjust;
823
            int keyframe_number;
824
        }
825
       https://github.com/MythTV/mythtv/blob/fixes/0.22/mythtv/libs/libmythtv/format.h */
826
0
    int32_t i_kfa_elements = 0;
827
0
    uint8_t *p_kfa_table = NULL;
828
829
0
    if( p_sys->exh.i_keyframe_adjust_offset > 0 )
830
0
    {
831
0
        msg_Dbg( p_demux, "seeking in stream to %"PRIi64, p_sys->exh.i_keyframe_adjust_offset );
832
0
        if( vlc_stream_Seek( p_demux->s, p_sys->exh.i_keyframe_adjust_offset ) )
833
0
        {
834
0
            free( p_seek_table );
835
0
            goto restore;
836
0
        }
837
838
0
        if( FrameHeaderLoad( p_demux, &fh ) )
839
0
        {
840
0
            free( p_seek_table );
841
0
            goto restore;
842
0
        }
843
844
0
        if( fh.i_type == 'K' && fh.i_length >= 8 )
845
0
        {
846
0
            p_kfa_table = malloc( fh.i_length );
847
848
0
            if( p_kfa_table == NULL )
849
0
            {
850
0
                free( p_seek_table );
851
0
                ret = VLC_ENOMEM;
852
0
                goto restore;
853
0
            }
854
855
0
            if( vlc_stream_Read( p_demux->s, p_kfa_table,
856
0
                                 fh.i_length ) != fh.i_length )
857
0
            {
858
0
                ret = VLC_EGENERIC;
859
0
                goto cleanup;
860
0
            }
861
862
0
            i_kfa_elements = fh.i_length / 8;
863
0
        }
864
0
    }
865
866
0
    if( i_kfa_elements > 0 )
867
0
        msg_Warn( p_demux, "untested keyframe adjust support, upload samples" );
868
869
0
    for( int32_t j = 0; j < i_seek_elements; j++)
870
0
    {
871
0
        const uint8_t *p_seek_entry = p_seek_table + j * 12;
872
#if 0
873
        const uint8_t* p = p_seek_entry;
874
        msg_Dbg( p_demux, "%x %x %x %x %x %x %x %x %x %x %x %x",
875
        p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11]);
876
#endif
877
0
        int32_t keyframe = GetDWLE( p_seek_entry + 8 );
878
0
        int32_t keyframe_delta;
879
0
        int32_t frame_delta;
880
0
        int32_t next_frame;
881
882
0
        if( keyframe < 0 || keyframe < last_keyframe ||
883
0
            ckd_sub( &keyframe_delta, keyframe, last_keyframe ) ||
884
0
            ckd_mul( &frame_delta, keyframe_delta,
885
0
                     p_sys->hdr.i_keyframe_distance ) ||
886
0
            ckd_add( &next_frame, frame, frame_delta ) )
887
0
        {
888
0
            msg_Warn( p_demux, "broken index with keyframe value %"PRIi32, keyframe );
889
0
            ret = VLC_EGENERIC;
890
0
            goto cleanup;
891
0
        }
892
893
0
        frame = next_frame;
894
895
0
        if( kfa_entry_id < i_kfa_elements )
896
0
        {
897
0
            const uint8_t *p_kfa_entry = &p_kfa_table[kfa_entry_id * 8];
898
0
            int32_t i_kfa_adjust = GetDWLE(p_kfa_entry + 0);
899
0
            int32_t i_kfa_keyframe = GetDWLE(p_kfa_entry + 4);
900
0
            if( i_kfa_keyframe == j )
901
0
            {
902
0
                if( i_kfa_adjust < frame )
903
0
                {
904
0
                    int32_t corrected_frame;
905
906
0
                    if( ckd_sub( &corrected_frame, frame, i_kfa_adjust ) )
907
0
                    {
908
0
                        msg_Warn( p_demux, "broken keyframe adjustment" );
909
0
                        ret = VLC_EGENERIC;
910
0
                        goto cleanup;
911
0
                    }
912
913
0
                    frame = corrected_frame;
914
0
                }
915
0
                else
916
0
                    frame = 0;
917
0
                msg_Dbg( p_demux, "corrected keyframe %"PRIi32" with current frame number %"PRIi32" (corrected with %"PRIi32")",
918
0
                         keyframe, frame, i_kfa_adjust );
919
0
                kfa_entry_id++;
920
0
            }
921
0
        }
922
923
0
        int64_t i_offset = GetQWLE( p_seek_entry );
924
925
0
        if( ( i_offset == 0 && frame != 0 ) || i_offset < 0 )
926
0
            msg_Dbg( p_demux, "invalid file offset %"PRIi32" %"PRIi64, keyframe, i_offset );
927
0
        else
928
0
        {
929
0
            vlc_tick_t i_time;
930
0
            if( !FrameToTime( frame, p_sys->hdr.d_fps, &i_time ) )
931
0
            {
932
0
                msg_Warn( p_demux, "index timestamp is out of range" );
933
0
                ret = VLC_EGENERIC;
934
0
                goto cleanup;
935
0
            }
936
0
            demux_IndexAppend( &p_sys->idx, i_time , i_offset );
937
#if 0
938
            msg_Dbg( p_demux, "adding entry position %"PRIi32" %"PRIi64 " file offset %"PRIi64, keyframe, i_time, i_offset );
939
#endif
940
0
        }
941
942
0
        last_keyframe = keyframe;
943
0
    }
944
945
0
    p_sys->b_index = p_sys->idx.i_idx > 0;
946
947
0
    if( !FrameToTime( frame + INT64_C(1), p_sys->hdr.d_fps, &p_sys->i_total_length ) )
948
0
    {
949
0
        p_sys->i_total_length = -1;
950
0
        msg_Warn( p_demux, "total time is out of range" );
951
0
        goto cleanup;
952
0
    }
953
954
0
    msg_Dbg( p_demux, "index table loaded (%"PRIi32" elements)", i_seek_elements );
955
956
0
    ret = VLC_SUCCESS;
957
958
0
cleanup:
959
0
    free ( p_kfa_table );
960
0
    free ( p_seek_table );
961
962
77
restore:
963
    /* Restore stream position */
964
77
    if( vlc_stream_Seek( p_demux->s, i_original_pos ) )
965
0
        return VLC_EGENERIC;
966
967
77
    return ret;
968
77
}
969
970
/*****************************************************************************/
971
128
#define DEMUX_INDEX_SIZE_MAX (100000)
972
static void demux_IndexInit( demux_index_t *p_idx )
973
357
{
974
357
    p_idx->i_idx = 0;
975
357
    p_idx->i_idx_max = 0;
976
357
    p_idx->idx = NULL;
977
357
}
978
static void demux_IndexClean( demux_index_t *p_idx )
979
357
{
980
357
    free( p_idx->idx );
981
357
    p_idx->idx = NULL;
982
357
}
983
static void demux_IndexAppend( demux_index_t *p_idx,
984
                               vlc_tick_t i_time, int64_t i_offset )
985
515
{
986
    /* Be sure to append new entry (we don't insert point) */
987
515
    if( p_idx->i_idx > 0 && p_idx->idx[p_idx->i_idx-1].i_time >= i_time )
988
385
        return;
989
990
    /* */
991
130
    if( p_idx->i_idx >= p_idx->i_idx_max )
992
128
    {
993
128
        if( p_idx->i_idx >= DEMUX_INDEX_SIZE_MAX )
994
0
        {
995
            /* Avoid too big index */
996
0
            const int64_t i_length = p_idx->idx[p_idx->i_idx-1].i_time -
997
0
                                                        p_idx->idx[0].i_time;
998
0
            const int i_count = DEMUX_INDEX_SIZE_MAX/2;
999
0
            int i, j;
1000
1001
            /* We try to reduce the resolution of the index by a factor 2 */
1002
0
            for( i = 1, j = 1; i < p_idx->i_idx; i++ )
1003
0
            {
1004
0
                if( p_idx->idx[i].i_time < j * i_length / i_count )
1005
0
                    continue;
1006
1007
0
                p_idx->idx[j++] = p_idx->idx[i];
1008
0
            }
1009
0
            p_idx->i_idx = j;
1010
1011
0
            if( p_idx->i_idx > 3 * DEMUX_INDEX_SIZE_MAX / 4 )
1012
0
            {
1013
                /* We haven't created enough space
1014
                 * (This method won't create a good index but work for sure) */
1015
0
                for( i = 0; i < p_idx->i_idx/2; i++ )
1016
0
                    p_idx->idx[i] = p_idx->idx[2*i];
1017
0
                p_idx->i_idx /= 2;
1018
0
            }
1019
0
        }
1020
128
        else
1021
128
        {
1022
128
            if(INT_MAX - 1000 < p_idx->i_idx_max ||
1023
128
               (SIZE_MAX / sizeof(demux_index_entry_t)) - p_idx->i_idx_max < 1000)
1024
0
                return;
1025
128
            size_t i_realloc = (1000 + p_idx->i_idx_max) * sizeof(demux_index_entry_t);
1026
128
            demux_index_entry_t *p_realloc = realloc( p_idx->idx, i_realloc );
1027
128
            if( !p_realloc )
1028
0
                return;
1029
128
            p_idx->i_idx_max += 1000;
1030
128
            p_idx->idx = p_realloc;
1031
128
        }
1032
128
    }
1033
1034
    /* */
1035
130
    p_idx->idx[p_idx->i_idx].i_time = i_time;
1036
130
    p_idx->idx[p_idx->i_idx].i_offset = i_offset;
1037
1038
130
    p_idx->i_idx++;
1039
130
}
1040
1041
static uint64_t demux_IndexConvertTime(demux_index_t *p_idx,
1042
                                       vlc_tick_t i_time)
1043
0
{
1044
0
    int i_min = 0;
1045
0
    int i_max = p_idx->i_idx-1;
1046
1047
    /* Empty index */
1048
0
    if( p_idx->i_idx <= 0 )
1049
0
        return -1;
1050
1051
    /* Special border case */
1052
0
    if( i_time <= p_idx->idx[0].i_time )
1053
0
        return p_idx->idx[0].i_offset;
1054
0
    if( i_time >= p_idx->idx[i_max].i_time )
1055
0
        return p_idx->idx[i_max].i_offset;
1056
1057
    /* Dicho */
1058
0
    for( ;; )
1059
0
    {
1060
0
        int i_med;
1061
1062
0
        if( i_max - i_min <= 1 )
1063
0
            break;
1064
1065
0
        i_med = (i_min+i_max)/2;
1066
0
        if( p_idx->idx[i_med].i_time < i_time )
1067
0
            i_min = i_med;
1068
0
        else if( p_idx->idx[i_med].i_time > i_time )
1069
0
            i_max = i_med;
1070
0
        else
1071
0
            return p_idx->idx[i_med].i_offset;
1072
0
    }
1073
1074
    /* return nearest in time */
1075
0
    if( i_time - p_idx->idx[i_min].i_time < p_idx->idx[i_max].i_time - i_time )
1076
0
        return p_idx->idx[i_min].i_offset;
1077
0
    else
1078
0
        return p_idx->idx[i_max].i_offset;
1079
0
}
1080
1081
1082
static uint64_t demux_IndexFindOffset(demux_index_t *p_idx, uint64_t i_offset)
1083
0
{
1084
0
    int i_min = 0;
1085
0
    int i_max = p_idx->i_idx-1;
1086
1087
    /* Empty index */
1088
0
    if( p_idx->i_idx <= 0 )
1089
0
        return -1;
1090
1091
    /* Special border case */
1092
0
    if( i_offset <= p_idx->idx[0].i_offset )
1093
0
        return p_idx->idx[0].i_offset;
1094
0
    if( i_offset == p_idx->idx[i_max].i_offset )
1095
0
        return p_idx->idx[i_max].i_offset;
1096
0
    if( i_offset > p_idx->idx[i_max].i_offset )
1097
0
        return -1;
1098
1099
    /* Dicho */
1100
0
    for( ;; )
1101
0
    {
1102
0
        int i_med;
1103
1104
0
        if( i_max - i_min <= 1 )
1105
0
            break;
1106
1107
0
        i_med = (i_min+i_max)/2;
1108
0
        if( p_idx->idx[i_med].i_offset < i_offset )
1109
0
            i_min = i_med;
1110
0
        else if( p_idx->idx[i_med].i_offset > i_offset )
1111
0
            i_max = i_med;
1112
0
        else
1113
0
            return p_idx->idx[i_med].i_offset;
1114
0
    }
1115
1116
    /* return nearest */
1117
0
    if( i_offset - p_idx->idx[i_min].i_offset < p_idx->idx[i_max].i_offset - i_offset )
1118
0
        return p_idx->idx[i_min].i_offset;
1119
0
    else
1120
0
        return p_idx->idx[i_max].i_offset;
1121
0
}
1122