vcd.c 17.9 KB
Newer Older
1
2
3
4
/*****************************************************************************
 * vcd.c : VCD input module for vlc
 *****************************************************************************
 * Copyright (C) 2000 VideoLAN
5
 * $Id: vcd.c,v 1.10 2002/11/06 15:41:29 jobi Exp $
6
7
8
9
10
11
12
 *
 * Author: Johan Bilien <jobi@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
13
 *
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdio.h>
#include <stdlib.h>

#include <vlc/vlc.h>
#include <vlc/input.h>

33
34
#include "../../demux/mpeg/system.h"

35
36
37
38
39
40
41
42
43
44
45
46
#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#endif

#include <string.h>

#include "vcd.h"

/* how many blocks VCDRead will read in each loop */
#define VCD_BLOCKS_ONCE 20
#define VCD_DATA_ONCE   (VCD_BLOCKS_ONCE * VCD_DATA_SIZE)

gbazin's avatar
   
gbazin committed
47
48
49
50
51
52
/*****************************************************************************
 * thread_vcd_data_t: VCD information
 *****************************************************************************/
typedef struct thread_vcd_data_s
{
    vcddev_t    *vcddev;                            /* vcd device descriptor */
53
    int         i_nb_tracks;                        /* Nb of tracks (titles) */
gbazin's avatar
   
gbazin committed
54
55
56
    int         i_track;                                    /* Current track */
    int         i_sector;                                  /* Current Sector */
    int *       p_sectors;                                  /* Track sectors */
57
58
59
    int         i_entries_nb;                      /* Number of entry points */
    int *       p_entries;                                   /* Entry points */
    vlc_bool_t  b_valid_ep;                       /* Valid entry points flag */
gbazin's avatar
   
gbazin committed
60
61
62
63
    vlc_bool_t  b_end_of_track;           /* If the end of track was reached */

} thread_vcd_data_t;

64
65
66
67
68
69
70
71
72
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  VCDOpen         ( vlc_object_t * );
static void VCDClose        ( vlc_object_t * );
static int  VCDRead         ( input_thread_t *, byte_t *, size_t );
static void VCDSeek         ( input_thread_t *, off_t );
static int  VCDSetArea      ( input_thread_t *, input_area_t * );
static int  VCDSetProgram   ( input_thread_t *, pgrm_descriptor_t * );
73
static int  VCDEntryPoints  ( input_thread_t * );
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

/*****************************************************************************
 * Module descriptior
 *****************************************************************************/
vlc_module_begin();
    set_description( _("VCD input module") );
    set_capability( "access", 80 );
    set_callbacks( VCDOpen, VCDClose );
    add_shortcut( "svcd" );
vlc_module_end();

/*
 * Data reading functions
 */

/*****************************************************************************
 * VCDOpen: open vcd
 *****************************************************************************/
static int VCDOpen( vlc_object_t *p_this )
{
    input_thread_t *        p_input = (input_thread_t *)p_this;
    char *                  psz_orig;
    char *                  psz_parser;
    char *                  psz_source;
    char *                  psz_next;
    thread_vcd_data_t *     p_vcd;
    int                     i;
    input_area_t *          p_area;
    int                     i_title = 1;
    int                     i_chapter = 1;

    p_input->pf_read = VCDRead;
    p_input->pf_seek = VCDSeek;
    p_input->pf_set_area = VCDSetArea;
    p_input->pf_set_program = VCDSetProgram;

gbazin's avatar
   
gbazin committed
110
111
112
113
114
115
#ifdef WIN32
    /* On Win32 we want the VCD access plugin to be explicitly requested,
     * we end up with lots of problems otherwise */
    if( !p_input->psz_access || !*p_input->psz_access ) return( -1 );
#endif

116
117
    /* parse the options passed in command line : */
    psz_orig = psz_parser = psz_source = strdup( p_input->psz_name );
118

119
120
121
122
    if( !psz_orig )
    {
        return( -1 );
    }
123

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
    while( *psz_parser && *psz_parser != '@' )
    {
        psz_parser++;
    }

    if( *psz_parser == '@' )
    {
        /* Found options */
        *psz_parser = '\0';
        ++psz_parser;

        i_title = (int)strtol( psz_parser, &psz_next, 10 );
        if( *psz_next )
        {
            psz_parser = psz_next + 1;
            i_chapter = (int)strtol( psz_parser, &psz_next, 10 );
        }

        i_title = i_title ? i_title : 1;
        i_chapter = i_chapter ? i_chapter : 1;
    }

    if( !*psz_source )
    {
        if( !p_input->psz_access )
        {
            free( psz_orig );
            return -1;
        }
        psz_source = config_GetPsz( p_input, "vcd" );
gbazin's avatar
   
gbazin committed
154
        if( !psz_source ) return -1;
155
156
157
158
159
160
161
    }

    p_vcd = malloc( sizeof(thread_vcd_data_t) );

    if( p_vcd == NULL )
    {
        msg_Err( p_input, "out of memory" );
gbazin's avatar
   
gbazin committed
162
        free( psz_source );
163
164
        return -1;
    }
165

166
    p_input->p_access_data = (void *)p_vcd;
167

168
    p_input->i_mtu = VCD_DATA_ONCE;
169

170
171
172
173
174
175
176
177
178
179
    vlc_mutex_lock( &p_input->stream.stream_lock );

    p_input->stream.b_pace_control = 1;

    p_input->stream.b_seekable = 1;
    p_input->stream.p_selected_area->i_size = 0;
    p_input->stream.p_selected_area->i_tell = 0;

    vlc_mutex_unlock( &p_input->stream.stream_lock );

gbazin's avatar
   
gbazin committed
180
    if( !(p_vcd->vcddev = ioctl_Open( p_this, psz_source )) )
181
    {
182
        msg_Warn( p_input, "could not open %s", psz_source );
gbazin's avatar
   
gbazin committed
183
184
        free( psz_source );
        free( p_vcd );
185
186
187
188
        return -1;
    }

    /* We read the Table Of Content information */
189
    p_vcd->i_nb_tracks = ioctl_GetTracksMap( VLC_OBJECT(p_input),
gbazin's avatar
   
gbazin committed
190
191
                                           p_vcd->vcddev, &p_vcd->p_sectors );
    free( psz_source );
192
    if( p_vcd->i_nb_tracks < 0 )
193
        msg_Err( p_input, "unable to count tracks" );
194
    else if( p_vcd->i_nb_tracks <= 1 )
195
        msg_Err( p_input, "no movie tracks found" );
196
    if( p_vcd->i_nb_tracks <= 1)
197
    {
gbazin's avatar
   
gbazin committed
198
        ioctl_Close( p_this, p_vcd->vcddev );
199
200
201
202
        free( p_vcd );
        return -1;
    }

203
204
205
206
207
208
209
210
211
212
    /* Allocate the entry points table */
    p_vcd->p_entries = malloc( p_vcd->i_nb_tracks * sizeof( int ) );

    if( p_vcd->p_entries == NULL )
    {
        msg_Err( p_input, "not enough memory" );
        ioctl_Close( p_this, p_vcd->vcddev );
        free( p_vcd );
    }

213
214
215
216
217
218
219
220
221
    /* Set stream and area data */
    vlc_mutex_lock( &p_input->stream.stream_lock );

    /* Initialize ES structures */
    input_InitStream( p_input, sizeof( stream_ps_data_t ) );

    /* disc input method */
    p_input->stream.i_method = INPUT_METHOD_VCD;

222
223
    p_input->stream.i_area_nb = 1;

224
#define area p_input->stream.pp_areas
225
    for( i = 1 ; i <= p_vcd->i_nb_tracks - 1 ; i++ )
226
227
228
229
230
231
232
233
234
235
236
237
    {
        input_AddArea( p_input );

        /* Titles are Program Chains */
        area[i]->i_id = i;

        /* Absolute start offset and size */
        area[i]->i_start = (off_t)p_vcd->p_sectors[i] * (off_t)VCD_DATA_SIZE;
        area[i]->i_size = (off_t)(p_vcd->p_sectors[i+1] - p_vcd->p_sectors[i])
                           * (off_t)VCD_DATA_SIZE;

        /* Number of chapters */
238
        area[i]->i_part_nb = 0;   /* will be the entry points */
239
240
        area[i]->i_part = 1;

241
242
243
        /* i_plugin_data is used to store which entry point is the first
         * of the track (area) */
        area[i]->i_plugin_data = 0;
244
245
246
247
248
    }
#undef area

    p_area = p_input->stream.pp_areas[i_title];

249
250
251
252
253
254
255
    p_vcd->b_valid_ep = 1;
    if( VCDEntryPoints( p_input ) < 0 )
    {
        msg_Warn( p_input, "could read entry points, will not use them" );
        p_vcd->b_valid_ep = 0;
    }

256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
    VCDSetArea( p_input, p_area );

    vlc_mutex_unlock( &p_input->stream.stream_lock );

    p_input->psz_demux = "ps";

    return 0;
}

/*****************************************************************************
 * VCDClose: closes vcd
 *****************************************************************************/
static void VCDClose( vlc_object_t *p_this )
{
    input_thread_t *   p_input = (input_thread_t *)p_this;
    thread_vcd_data_t *p_vcd = (thread_vcd_data_t *)p_input->p_access_data;

gbazin's avatar
   
gbazin committed
273
    ioctl_Close( p_this, p_vcd->vcddev );
274
275
276
277
278
279
280
281
282
    free( p_vcd );
}

/*****************************************************************************
 * VCDRead: reads from the VCD into PES packets.
 *****************************************************************************
 * Returns -1 in case of error, 0 in case of EOF, otherwise the number of
 * bytes.
 *****************************************************************************/
283
static int VCDRead( input_thread_t * p_input, byte_t * p_buffer,
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
                     size_t i_len )
{
    thread_vcd_data_t *     p_vcd;
    int                     i_blocks;
    int                     i_index;
    int                     i_read;
    byte_t                  p_last_sector[ VCD_DATA_SIZE ];

    p_vcd = (thread_vcd_data_t *)p_input->p_access_data;

    i_read = 0;

    /* Compute the number of blocks we have to read */

    i_blocks = i_len / VCD_DATA_SIZE;

300
    for ( i_index = 0 ; i_index < i_blocks ; i_index++ )
301
    {
gbazin's avatar
   
gbazin committed
302
        if ( ioctl_ReadSector( VLC_OBJECT(p_input), p_vcd->vcddev,
303
                    p_vcd->i_sector, p_buffer + i_index * VCD_DATA_SIZE ) < 0 )
304
305
306
307
308
309
310
311
312
        {
            msg_Err( p_input, "could not read sector %d", p_vcd->i_sector );
            return -1;
        }

        p_vcd->i_sector ++;
        if ( p_vcd->i_sector == p_vcd->p_sectors[p_vcd->i_track + 1] )
        {
            input_area_t *p_area;
313
314

            if ( p_vcd->i_track >= p_vcd->i_nb_tracks - 1 )
315
                return 0; /* EOF */
316
317

            vlc_mutex_lock( &p_input->stream.stream_lock );
318
319
            p_area = p_input->stream.pp_areas[
                    p_input->stream.p_selected_area->i_id + 1 ];
320

321
            msg_Dbg( p_input, "new title" );
322

323
324
            p_area->i_part = 1;
            VCDSetArea( p_input, p_area );
325
            vlc_mutex_unlock( &p_input->stream.stream_lock );
326
        }
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349

        /* Update chapter */
        else if( p_vcd->b_valid_ep &&
                /* FIXME kludge so that read does not update chapter
                 * when a manual chapter change was requested and not
                 * yet accomplished */
                !p_input->stream.p_new_area )
        {
            int i_entry;

            vlc_mutex_lock( &p_input->stream.stream_lock );
            i_entry = p_input->stream.p_selected_area->i_plugin_data
                /* 1st entry point of the track (area)*/
                        + p_input->stream.p_selected_area->i_part - 1;
            if( i_entry + 1 < p_vcd->i_entries_nb &&
                    p_vcd->i_sector >= p_vcd->p_entries[i_entry + 1] )
            {
                msg_Dbg( p_input, "new chapter" );
                p_input->stream.p_selected_area->i_part ++;
            }
            vlc_mutex_unlock( &p_input->stream.stream_lock );
        }

350
351
        i_read += VCD_DATA_SIZE;
    }
352

353
    if ( i_len % VCD_DATA_SIZE ) /* this should not happen */
354
    {
gbazin's avatar
   
gbazin committed
355
        if ( ioctl_ReadSector( VLC_OBJECT(p_input), p_vcd->vcddev,
356
                               p_vcd->i_sector, p_last_sector ) < 0 )
357
358
359
360
        {
            msg_Err( p_input, "could not read sector %d", p_vcd->i_sector );
            return -1;
        }
361

362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
        p_input->p_vlc->pf_memcpy( p_buffer + i_blocks * VCD_DATA_SIZE,
                                   p_last_sector, i_len % VCD_DATA_SIZE );
        i_read += i_len % VCD_DATA_SIZE;
    }

    return i_read;
}


/*****************************************************************************
 * VCDSetProgram: Does nothing since a VCD is mono_program
 *****************************************************************************/
static int VCDSetProgram( input_thread_t * p_input,
                          pgrm_descriptor_t * p_program)
{
    return 0;
}


/*****************************************************************************
 * VCDSetArea: initialize input data for title x, chapter y.
 * It should be called for each user navigation request.
 ****************************************************************************/
static int VCDSetArea( input_thread_t * p_input, input_area_t * p_area )
{
    thread_vcd_data_t *     p_vcd;

    p_vcd = (thread_vcd_data_t*)p_input->p_access_data;

    /* we can't use the interface slider until initilization is complete */
    p_input->stream.b_seekable = 0;

    if( p_area != p_input->stream.p_selected_area )
    {
        /* Reset the Chapter position of the current title */
        p_input->stream.p_selected_area->i_part = 1;
        p_input->stream.p_selected_area->i_tell = 0;

        /* Change the default area */
        p_input->stream.p_selected_area = p_area;

        /* Change the current track */
        /* The first track is not a valid one  */
        p_vcd->i_track = p_area->i_id;
        p_vcd->i_sector = p_vcd->p_sectors[p_vcd->i_track];
    }

409
410
411
412
413
414
415
416
417
418
419
420
421
422
    if( p_vcd->b_valid_ep )
    {
        int i_entry = p_area->i_plugin_data /* 1st entry point of
                                               the track (area)*/
                            + p_area->i_part - 1;
        p_vcd->i_sector = p_vcd->p_entries[i_entry];
    }
    else
        p_vcd->i_sector = p_vcd->p_sectors[p_vcd->i_track];

    p_input->stream.p_selected_area->i_tell =
        (off_t)p_vcd->i_sector * (off_t)VCD_DATA_SIZE
         - p_input->stream.p_selected_area->i_start;

423
424
425
426
427
428
429
430
431
432
433
434
435
436
    /* warn interface that something has changed */
    p_input->stream.b_seekable = 1;
    p_input->stream.b_changed = 1;

    return 0;
}


/****************************************************************************
 * VCDSeek
 ****************************************************************************/
static void VCDSeek( input_thread_t * p_input, off_t i_off )
{
    thread_vcd_data_t *               p_vcd;
437
    int                               i_index;
438
439
440
441
442
443

    p_vcd = (thread_vcd_data_t *) p_input->p_access_data;

    p_vcd->i_sector = p_vcd->p_sectors[p_vcd->i_track]
                       + i_off / (off_t)VCD_DATA_SIZE;

444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    vlc_mutex_lock( &p_input->stream.stream_lock );
#define p_area p_input->stream.p_selected_area
    /* Find chapter */
    if( p_vcd->b_valid_ep )
    {
        for( i_index = 0 ; i_index < p_area->i_part_nb - 1 ; i_index ++ )
        {
            if( p_vcd->i_sector < p_vcd->p_entries[p_area->i_plugin_data
                + i_index + 1] )
            {
                p_area->i_part = i_index;
                break;
            }
        }
    }
#undef p_area

    p_input->stream.p_selected_area->i_tell =
462
463
        (off_t)p_vcd->i_sector * (off_t)VCD_DATA_SIZE
         - p_input->stream.p_selected_area->i_start;
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
    vlc_mutex_unlock( &p_input->stream.stream_lock );
}

/*****************************************************************************
 * VCDEntryPoints: Reads the information about the entry points on the disc.
 *****************************************************************************/
static int VCDEntryPoints( input_thread_t * p_input )
{
    thread_vcd_data_t *               p_vcd;
    byte_t *                          p_sector;
    entries_sect_t                    entries;
    uint16_t                          i_nb;
    int                               i, i_entry_index = 0;
    int                               i_previous_track = -1;

    p_vcd = (thread_vcd_data_t *) p_input->p_access_data;

    p_sector = malloc( VCD_DATA_SIZE * sizeof( byte_t ) );
    if( p_sector == NULL )
    {
        msg_Err( p_input, "not enough memory for entry points treatment" );
        return -1;
    }

    if( ioctl_ReadSector( VLC_OBJECT(p_input), p_vcd->vcddev,
                                VCD_ENTRIES_SECTOR, p_sector ) < 0 )
    {
        msg_Err( p_input, "could not read entry points sector" );
        free( p_sector );
        return( -1 );
    }

    memcpy( &entries, p_sector, CD_SECTOR_SIZE );
    free( p_sector );

    if( (i_nb = U16_AT( &entries.i_entries_nb )) > 500 )
    {
        msg_Err( p_input, "invalid entry points number" );
        return( -1 );
    }

    p_vcd->p_entries = malloc( sizeof( int ) * i_nb );
    if( p_vcd->p_entries == NULL )
    {
        msg_Err( p_input, "not enough memory for entry points treatment" );
        return -1;
    }

    if( strncmp( entries.psz_id, "ENTRYVCD", sizeof( entries.psz_id ) )
     && strncmp( entries.psz_id, "ENTRYSVD", sizeof( entries.psz_id ) ))
    {
        msg_Err( p_input, "unrecognized entry points format" );
        free( p_vcd->p_entries );
        return -1;
    }

    p_vcd->i_entries_nb = 0;

#define i_track entries.entry[i].i_track
    for( i = 0 ; i < i_nb ; i++ )
    {
        if( i_track <= p_input->stream.i_area_nb )
        {
            p_vcd->p_entries[i_entry_index] =
                (MSF_TO_LBA2( BCD_TO_BIN( entries.entry[i].msf.minute ),
                              BCD_TO_BIN( entries.entry[i].msf.second ),
                              BCD_TO_BIN( entries.entry[i].msf.frame  ) ));
            p_input->stream.pp_areas[i_track-1]->i_part_nb ++;
            /* if this entry belongs to a new track */
            if( i_track != i_previous_track )
            {
                /* i_plugin_data is used to store the first entry of the area*/
                p_input->stream.pp_areas[i_track-1]->i_plugin_data =
                                                            i_entry_index;
                i_previous_track = i_track;
            }
            i_entry_index ++;
            p_vcd->i_entries_nb ++;
        }
        else
            msg_Warn( p_input, "wrong track number found in entry points" );
    }
#undef i_track
    return 0;
548
}