* ALL: experimental code for stream (dvd) navigation through object variables.
[vlc.git] / modules / access / vcd / vcd.c
1 /*****************************************************************************
2  * vcd.c : VCD input module for vlc
3  *****************************************************************************
4  * Copyright (C) 2000 VideoLAN
5  * $Id: vcd.c,v 1.16 2003/03/11 23:56:54 gbazin Exp $
6  *
7  * Author: Johan Bilien <jobi@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdio.h>
28 #include <stdlib.h>
29
30 #include <vlc/vlc.h>
31 #include <vlc/input.h>
32
33 #include "../../demux/mpeg/system.h"
34
35 #ifdef HAVE_UNISTD_H
36 #   include <unistd.h>
37 #endif
38
39 #include <string.h>
40
41 #include "vcd.h"
42
43 /* how many blocks VCDRead will read in each loop */
44 #define VCD_BLOCKS_ONCE 20
45 #define VCD_DATA_ONCE   (VCD_BLOCKS_ONCE * VCD_DATA_SIZE)
46
47 /*****************************************************************************
48  * thread_vcd_data_t: VCD information
49  *****************************************************************************/
50 typedef struct thread_vcd_data_s
51 {
52     vcddev_t    *vcddev;                            /* vcd device descriptor */
53     int         i_nb_tracks;                        /* Nb of tracks (titles) */
54     int         i_track;                                    /* Current track */
55     int         i_sector;                                  /* Current Sector */
56     int *       p_sectors;                                  /* Track sectors */
57     int         i_entries_nb;                      /* Number of entry points */
58     int *       p_entries;                                   /* Entry points */
59     vlc_bool_t  b_valid_ep;                       /* Valid entry points flag */
60     vlc_bool_t  b_end_of_track;           /* If the end of track was reached */
61
62 } thread_vcd_data_t;
63
64 /*****************************************************************************
65  * Local prototypes
66  *****************************************************************************/
67 static int  VCDOpen         ( vlc_object_t * );
68 static void VCDClose        ( vlc_object_t * );
69 static int  VCDRead         ( input_thread_t *, byte_t *, size_t );
70 static void VCDSeek         ( input_thread_t *, off_t );
71 static int  VCDSetArea      ( input_thread_t *, input_area_t * );
72 static int  VCDSetProgram   ( input_thread_t *, pgrm_descriptor_t * );
73 static int  VCDEntryPoints  ( input_thread_t * );
74
75 /*****************************************************************************
76  * Module descriptior
77  *****************************************************************************/
78 vlc_module_begin();
79     set_description( _("VCD input module") );
80     set_capability( "access", 80 );
81     set_callbacks( VCDOpen, VCDClose );
82     add_shortcut( "svcd" );
83 vlc_module_end();
84
85 /*
86  * Data reading functions
87  */
88
89 /*****************************************************************************
90  * VCDOpen: open vcd
91  *****************************************************************************/
92 static int VCDOpen( vlc_object_t *p_this )
93 {
94     input_thread_t *        p_input = (input_thread_t *)p_this;
95     char *                  psz_orig;
96     char *                  psz_parser;
97     char *                  psz_source;
98     char *                  psz_next;
99     thread_vcd_data_t *     p_vcd;
100     int                     i;
101     input_area_t *          p_area;
102     int                     i_title = 1;
103     int                     i_chapter = 1;
104
105     p_input->pf_read = VCDRead;
106     p_input->pf_seek = VCDSeek;
107     p_input->pf_set_area = VCDSetArea;
108     p_input->pf_set_program = VCDSetProgram;
109
110 #ifdef WIN32
111     /* On Win32 we want the VCD access plugin to be explicitly requested,
112      * we end up with lots of problems otherwise */
113     if( !p_input->psz_access || !*p_input->psz_access ) return( -1 );
114 #endif
115
116     /* parse the options passed in command line : */
117     psz_orig = psz_parser = psz_source = strdup( p_input->psz_name );
118
119     if( !psz_orig )
120     {
121         return( -1 );
122     }
123
124     while( *psz_parser && *psz_parser != '@' )
125     {
126         psz_parser++;
127     }
128
129     if( *psz_parser == '@' )
130     {
131         /* Found options */
132         *psz_parser = '\0';
133         ++psz_parser;
134
135         i_title = (int)strtol( psz_parser, &psz_next, 10 );
136         if( *psz_next )
137         {
138             psz_parser = psz_next + 1;
139             i_chapter = (int)strtol( psz_parser, &psz_next, 10 );
140         }
141
142         i_title = i_title ? i_title : 1;
143         i_chapter = i_chapter ? i_chapter : 1;
144     }
145
146     if( !*psz_source )
147     {
148         if( !p_input->psz_access )
149         {
150             free( psz_orig );
151             return -1;
152         }
153         psz_source = config_GetPsz( p_input, "vcd" );
154         if( !psz_source ) return -1;
155     }
156
157     p_vcd = malloc( sizeof(thread_vcd_data_t) );
158
159     if( p_vcd == NULL )
160     {
161         msg_Err( p_input, "out of memory" );
162         free( psz_source );
163         return -1;
164     }
165
166     p_input->p_access_data = (void *)p_vcd;
167
168     p_input->i_mtu = VCD_DATA_ONCE;
169
170     vlc_mutex_lock( &p_input->stream.stream_lock );
171
172     p_input->stream.b_pace_control = 1;
173     p_input->stream.b_seekable = 1;
174     p_input->stream.b_connected = 1;
175     p_input->stream.p_selected_area->i_size = 0;
176     p_input->stream.p_selected_area->i_tell = 0;
177
178     vlc_mutex_unlock( &p_input->stream.stream_lock );
179
180     if( !(p_vcd->vcddev = ioctl_Open( p_this, psz_source )) )
181     {
182         msg_Warn( p_input, "could not open %s", psz_source );
183         free( psz_source );
184         free( p_vcd );
185         return -1;
186     }
187
188     /* We read the Table Of Content information */
189     p_vcd->i_nb_tracks = ioctl_GetTracksMap( VLC_OBJECT(p_input),
190                                            p_vcd->vcddev, &p_vcd->p_sectors );
191     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     {
198         ioctl_Close( p_this, p_vcd->vcddev );
199         free( p_vcd );
200         return -1;
201     }
202
203     /* Allocate the entry points table */
204     p_vcd->p_entries = malloc( p_vcd->i_nb_tracks * sizeof( int ) );
205
206     if( p_vcd->p_entries == NULL )
207     {
208         msg_Err( p_input, "not enough memory" );
209         ioctl_Close( p_this, p_vcd->vcddev );
210         free( p_vcd );
211     }
212
213     /* Set stream and area data */
214     vlc_mutex_lock( &p_input->stream.stream_lock );
215
216     /* Initialize ES structures */
217     input_InitStream( p_input, sizeof( stream_ps_data_t ) );
218
219     /* disc input method */
220     p_input->stream.i_method = INPUT_METHOD_VCD;
221
222     p_input->stream.i_area_nb = 1;
223
224 #define area p_input->stream.pp_areas
225     for( i = 1 ; i <= p_vcd->i_nb_tracks - 1 ; i++ )
226     {
227         /* Titles are Program Chains */
228         input_AddArea( p_input, i, 1 );
229
230         /* Absolute start offset and size */
231         area[i]->i_start = (off_t)p_vcd->p_sectors[i] * (off_t)VCD_DATA_SIZE;
232         area[i]->i_size = (off_t)(p_vcd->p_sectors[i+1] - p_vcd->p_sectors[i])
233                            * (off_t)VCD_DATA_SIZE;
234
235         /* Default Chapter */
236         area[i]->i_part = 1;
237
238         /* i_plugin_data is used to store which entry point is the first
239          * of the track (area) */
240         area[i]->i_plugin_data = 0;
241     }
242 #undef area
243
244     p_area = p_input->stream.pp_areas[i_title];
245
246     p_vcd->b_valid_ep = 1;
247     if( VCDEntryPoints( p_input ) < 0 )
248     {
249         msg_Warn( p_input, "could not read entry points, will not use them" );
250         p_vcd->b_valid_ep = 0;
251     }
252
253     VCDSetArea( p_input, p_area );
254
255     vlc_mutex_unlock( &p_input->stream.stream_lock );
256
257     if( !p_input->psz_demux || !*p_input->psz_demux )
258     {
259         p_input->psz_demux = "ps";
260     }
261
262     return 0;
263 }
264
265 /*****************************************************************************
266  * VCDClose: closes vcd
267  *****************************************************************************/
268 static void VCDClose( vlc_object_t *p_this )
269 {
270     input_thread_t *   p_input = (input_thread_t *)p_this;
271     thread_vcd_data_t *p_vcd = (thread_vcd_data_t *)p_input->p_access_data;
272
273     ioctl_Close( p_this, p_vcd->vcddev );
274     free( p_vcd );
275 }
276
277 /*****************************************************************************
278  * VCDRead: reads from the VCD into PES packets.
279  *****************************************************************************
280  * Returns -1 in case of error, 0 in case of EOF, otherwise the number of
281  * bytes.
282  *****************************************************************************/
283 static int VCDRead( input_thread_t * p_input, byte_t * p_buffer,
284                      size_t i_len )
285 {
286     thread_vcd_data_t *     p_vcd;
287     int                     i_blocks;
288     int                     i_index;
289     int                     i_read;
290     byte_t                  p_last_sector[ VCD_DATA_SIZE ];
291
292     p_vcd = (thread_vcd_data_t *)p_input->p_access_data;
293
294     i_read = 0;
295
296     /* Compute the number of blocks we have to read */
297
298     i_blocks = i_len / VCD_DATA_SIZE;
299
300     for ( i_index = 0 ; i_index < i_blocks ; i_index++ )
301     {
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             msg_Err( p_input, "could not read sector %d", p_vcd->i_sector );
306             return -1;
307         }
308
309         p_vcd->i_sector ++;
310         if ( p_vcd->i_sector == p_vcd->p_sectors[p_vcd->i_track + 1] )
311         {
312             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             p_area = p_input->stream.pp_areas[
319                     p_input->stream.p_selected_area->i_id + 1 ];
320
321             msg_Dbg( p_input, "new title" );
322
323             p_area->i_part = 1;
324             VCDSetArea( p_input, p_area );
325             vlc_mutex_unlock( &p_input->stream.stream_lock );
326         }
327
328         /* Update chapter */
329         else if( p_vcd->b_valid_ep &&
330                 /* FIXME kludge so that read does not update chapter
331                  * when a manual chapter change was requested and not
332                  * yet accomplished */
333                 !p_input->stream.p_new_area )
334         {
335             int i_entry;
336
337             vlc_mutex_lock( &p_input->stream.stream_lock );
338             i_entry = p_input->stream.p_selected_area->i_plugin_data
339                 /* 1st entry point of the track (area)*/
340                         + p_input->stream.p_selected_area->i_part - 1;
341             if( i_entry + 1 < p_vcd->i_entries_nb &&
342                     p_vcd->i_sector >= p_vcd->p_entries[i_entry + 1] )
343             {
344                 msg_Dbg( p_input, "new chapter" );
345                 p_input->stream.p_selected_area->i_part ++;
346             }
347             vlc_mutex_unlock( &p_input->stream.stream_lock );
348         }
349
350         i_read += VCD_DATA_SIZE;
351     }
352
353     if ( i_len % VCD_DATA_SIZE ) /* this should not happen */
354     {
355         if ( ioctl_ReadSector( VLC_OBJECT(p_input), p_vcd->vcddev,
356                                p_vcd->i_sector, p_last_sector ) < 0 )
357         {
358             msg_Err( p_input, "could not read sector %d", p_vcd->i_sector );
359             return -1;
360         }
361
362         p_input->p_vlc->pf_memcpy( p_buffer + i_blocks * VCD_DATA_SIZE,
363                                    p_last_sector, i_len % VCD_DATA_SIZE );
364         i_read += i_len % VCD_DATA_SIZE;
365     }
366
367     return i_read;
368 }
369
370
371 /*****************************************************************************
372  * VCDSetProgram: Does nothing since a VCD is mono_program
373  *****************************************************************************/
374 static int VCDSetProgram( input_thread_t * p_input,
375                           pgrm_descriptor_t * p_program)
376 {
377     return 0;
378 }
379
380
381 /*****************************************************************************
382  * VCDSetArea: initialize input data for title x, chapter y.
383  * It should be called for each user navigation request.
384  ****************************************************************************/
385 static int VCDSetArea( input_thread_t * p_input, input_area_t * p_area )
386 {
387     thread_vcd_data_t *     p_vcd;
388     vlc_value_t val;
389
390     p_vcd = (thread_vcd_data_t*)p_input->p_access_data;
391
392     /* we can't use the interface slider until initilization is complete */
393     p_input->stream.b_seekable = 0;
394
395     if( p_area != p_input->stream.p_selected_area )
396     {
397         unsigned int i;
398
399         /* Reset the Chapter position of the current title */
400         p_input->stream.p_selected_area->i_part = 1;
401         p_input->stream.p_selected_area->i_tell = 0;
402
403         /* Change the default area */
404         p_input->stream.p_selected_area = p_area;
405
406         /* Change the current track */
407         /* The first track is not a valid one  */
408         p_vcd->i_track = p_area->i_id;
409         p_vcd->i_sector = p_vcd->p_sectors[p_vcd->i_track];
410
411         /* Update the navigation variables without triggering a callback */
412         val.i_int = p_area->i_id;
413         var_Change( p_input, "title", VLC_VAR_SETVALUE, &val );
414         var_Change( p_input, "chapter", VLC_VAR_CLEARCHOICES, NULL );
415         for( i = 1; i <= p_area->i_part_nb; i++ )
416         {
417             val.i_int = i;
418             var_Change( p_input, "chapter", VLC_VAR_ADDCHOICE, &val );
419         }
420     }
421
422     if( p_vcd->b_valid_ep )
423     {
424         int i_entry = p_area->i_plugin_data /* 1st entry point of
425                                                the track (area)*/
426                             + p_area->i_part - 1;
427         p_vcd->i_sector = p_vcd->p_entries[i_entry];
428     }
429     else
430         p_vcd->i_sector = p_vcd->p_sectors[p_vcd->i_track];
431
432     p_input->stream.p_selected_area->i_tell =
433         (off_t)p_vcd->i_sector * (off_t)VCD_DATA_SIZE
434          - p_input->stream.p_selected_area->i_start;
435
436     /* warn interface that something has changed */
437     p_input->stream.b_seekable = 1;
438     p_input->stream.b_changed = 1;
439
440     /* Update the navigation variables without triggering a callback */
441     val.i_int = p_area->i_part;
442     var_Change( p_input, "chapter", VLC_VAR_SETVALUE, &val );
443
444     return 0;
445 }
446
447
448 /****************************************************************************
449  * VCDSeek
450  ****************************************************************************/
451 static void VCDSeek( input_thread_t * p_input, off_t i_off )
452 {
453     thread_vcd_data_t * p_vcd;
454     unsigned int i_index;
455
456     p_vcd = (thread_vcd_data_t *) p_input->p_access_data;
457
458     p_vcd->i_sector = p_vcd->p_sectors[p_vcd->i_track]
459                        + i_off / (off_t)VCD_DATA_SIZE;
460
461     vlc_mutex_lock( &p_input->stream.stream_lock );
462 #define p_area p_input->stream.p_selected_area
463     /* Find chapter */
464     if( p_vcd->b_valid_ep )
465     {
466         for( i_index = 0 ; i_index < p_area->i_part_nb - 1 ; i_index ++ )
467         {
468             if( p_vcd->i_sector < p_vcd->p_entries[p_area->i_plugin_data
469                 + i_index + 1] )
470             {
471                 p_area->i_part = i_index;
472                 break;
473             }
474         }
475     }
476 #undef p_area
477
478     p_input->stream.p_selected_area->i_tell =
479         (off_t)p_vcd->i_sector * (off_t)VCD_DATA_SIZE
480          - p_input->stream.p_selected_area->i_start;
481     vlc_mutex_unlock( &p_input->stream.stream_lock );
482 }
483
484 /*****************************************************************************
485  * VCDEntryPoints: Reads the information about the entry points on the disc.
486  *****************************************************************************/
487 static int VCDEntryPoints( input_thread_t * p_input )
488 {
489     thread_vcd_data_t *               p_vcd;
490     byte_t *                          p_sector;
491     entries_sect_t                    entries;
492     uint16_t                          i_nb;
493     int                               i, i_entry_index = 0;
494     int                               i_previous_track = -1;
495
496     p_vcd = (thread_vcd_data_t *) p_input->p_access_data;
497
498     p_sector = malloc( VCD_DATA_SIZE * sizeof( byte_t ) );
499     if( p_sector == NULL )
500     {
501         msg_Err( p_input, "not enough memory for entry points treatment" );
502         return -1;
503     }
504
505     if( ioctl_ReadSector( VLC_OBJECT(p_input), p_vcd->vcddev,
506                                 VCD_ENTRIES_SECTOR, p_sector ) < 0 )
507     {
508         msg_Err( p_input, "could not read entry points sector" );
509         free( p_sector );
510         return( -1 );
511     }
512
513     memcpy( &entries, p_sector, CD_SECTOR_SIZE );
514     free( p_sector );
515
516     if( (i_nb = U16_AT( &entries.i_entries_nb )) > 500 )
517     {
518         msg_Err( p_input, "invalid entry points number" );
519         return( -1 );
520     }
521
522     p_vcd->p_entries = malloc( sizeof( int ) * i_nb );
523     if( p_vcd->p_entries == NULL )
524     {
525         msg_Err( p_input, "not enough memory for entry points treatment" );
526         return -1;
527     }
528
529     if( strncmp( entries.psz_id, "ENTRYVCD", sizeof( entries.psz_id ) )
530      && strncmp( entries.psz_id, "ENTRYSVD", sizeof( entries.psz_id ) ))
531     {
532         msg_Err( p_input, "unrecognized entry points format" );
533         free( p_vcd->p_entries );
534         return -1;
535     }
536
537     p_vcd->i_entries_nb = 0;
538
539 #define i_track BCD_TO_BIN(entries.entry[i].i_track)
540     for( i = 0 ; i < i_nb ; i++ )
541     {
542         if( i_track <= p_input->stream.i_area_nb )
543         {
544             p_vcd->p_entries[i_entry_index] =
545                 (MSF_TO_LBA2( BCD_TO_BIN( entries.entry[i].msf.minute ),
546                               BCD_TO_BIN( entries.entry[i].msf.second ),
547                               BCD_TO_BIN( entries.entry[i].msf.frame  ) ));
548             p_input->stream.pp_areas[i_track-1]->i_part_nb ++;
549             /* if this entry belongs to a new track */
550             if( i_track != i_previous_track )
551             {
552                 /* i_plugin_data is used to store the first entry of the area*/
553                 p_input->stream.pp_areas[i_track-1]->i_plugin_data =
554                                                             i_entry_index;
555                 i_previous_track = i_track;
556             }
557             i_entry_index ++;
558             p_vcd->i_entries_nb ++;
559         }
560         else
561             msg_Warn( p_input, "wrong track number found in entry points" );
562     }
563 #undef i_track
564     return 0;
565 }