1 /*****************************************************************************
2  * vcd.c : VCD input module for vlc
3  *****************************************************************************
4  * Copyright © 2000-2011 VLC authors and VideoLAN
5  * $Id: abdeaa624628b775c289a8f4712b4801ed390884 $
6  *
7  * Author: Johan Bilien <jobi@via.ecp.fr>
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 
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31 
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_input.h>
35 #include <vlc_access.h>
36 #include <vlc_charset.h>
37 
38 #include "cdrom.h"
39 
40 /*****************************************************************************
41  * Module descriptior
42  *****************************************************************************/
43 static int  Open ( vlc_object_t * );
44 static void Close( vlc_object_t * );
45 
46 vlc_module_begin ()
47     set_shortname( N_("VCD"))
48     set_description( N_("VCD input") )
49     set_capability( "access", 60 )
50     set_callbacks( Open, Close )
51     set_category( CAT_INPUT )
52     set_subcategory( SUBCAT_INPUT_ACCESS )
53 
54     add_usage_hint( N_("[vcd:][device][#[title][,[chapter]]]") )
55     add_shortcut( "vcd", "svcd" )
56 vlc_module_end ()
57 
58 /*****************************************************************************
59  * Local prototypes
60  *****************************************************************************/
61 
62 /* how many blocks VCDRead will read in each loop */
63 #define VCD_BLOCKS_ONCE 20
64 #define VCD_DATA_ONCE   (VCD_BLOCKS_ONCE * VCD_DATA_SIZE)
65 
66 struct access_sys_t
67 {
68     vcddev_t    *vcddev;                            /* vcd device descriptor */
69     uint64_t    offset;
70 
71     /* Title infos */
72     int           i_titles;
73     struct
74     {
75         uint64_t *seekpoints;
76         size_t    count;
77     } titles[99];                        /* No more that 99 track in a vcd ? */
78     int         i_current_title;
79     unsigned    i_current_seekpoint;
80 
81     int         i_sector;                                  /* Current Sector */
82     int         *p_sectors;                                 /* Track sectors */
83 };
84 
85 static block_t *Block( stream_t *, bool * );
86 static int      Seek( stream_t *, uint64_t );
87 static int      Control( stream_t *, int, va_list );
88 static int      EntryPoints( stream_t * );
89 
90 /*****************************************************************************
91  * VCDOpen: open vcd
92  *****************************************************************************/
Open(vlc_object_t * p_this)93 static int Open( vlc_object_t *p_this )
94 {
95     stream_t     *p_access = (stream_t *)p_this;
96     access_sys_t *p_sys;
97     if( p_access->psz_filepath == NULL )
98         return VLC_EGENERIC;
99 
100     char *psz_dup = ToLocaleDup( p_access->psz_filepath );
101     char *psz;
102     int i_title = 0;
103     int i_chapter = 0;
104     vcddev_t *vcddev;
105 
106     /* Command line: vcd://[dev_path][#title[,chapter]] */
107     if( ( psz = strchr( psz_dup, '#' ) ) )
108     {
109         *psz++ = '\0';
110 
111         i_title = strtol( psz, &psz, 0 );
112         if( *psz )
113             i_chapter = strtol( psz+1, &psz, 0 );
114     }
115 
116     if( *psz_dup == '\0' )
117     {
118         free( psz_dup );
119 
120         /* Only when selected */
121         if( strcmp( p_access->psz_name, "vcd" ) &&
122             strcmp( p_access->psz_name, "svcd" ) )
123             return VLC_EGENERIC;
124 
125         psz_dup = var_CreateGetString( p_access, "vcd" );
126         if( *psz_dup == '\0' )
127         {
128             free( psz_dup );
129             return VLC_EGENERIC;
130         }
131     }
132 
133 #if defined( _WIN32 ) || defined( __OS2__ )
134     if( psz_dup[0] && psz_dup[1] == ':' &&
135         psz_dup[2] == '\\' && psz_dup[3] == '\0' ) psz_dup[2] = '\0';
136 #endif
137 
138     /* Open VCD */
139     vcddev = ioctl_Open( p_this, psz_dup );
140     free( psz_dup );
141     if( !vcddev )
142         return VLC_EGENERIC;
143 
144     /* Set up p_access */
145     p_access->p_sys = p_sys = calloc( 1, sizeof( access_sys_t ) );
146     if( unlikely(!p_sys ))
147         goto error;
148     p_sys->vcddev = vcddev;
149     p_sys->offset = 0;
150 
151     for( size_t i = 0; i < ARRAY_SIZE(p_sys->titles); i++ )
152         p_sys->titles[i].seekpoints = NULL;
153 
154     /* We read the Table Of Content information */
155     p_sys->i_titles = ioctl_GetTracksMap( VLC_OBJECT(p_access),
156                                           p_sys->vcddev, &p_sys->p_sectors );
157     if( p_sys->i_titles < 0 )
158     {
159         msg_Err( p_access, "unable to count tracks" );
160         goto error;
161     }
162     else if( p_sys->i_titles <= 1 )
163     {
164         msg_Err( p_access, "no movie tracks found" );
165         goto error;
166     }
167 
168     /* The first title isn't usable */
169     p_sys->i_titles--;
170 
171     for( int i = 0; i < p_sys->i_titles; i++ )
172     {
173         msg_Dbg( p_access, "title[%d] start=%d", i, p_sys->p_sectors[1+i] );
174         msg_Dbg( p_access, "title[%d] end=%d", i, p_sys->p_sectors[i+2] );
175     }
176 
177     /* Map entry points into chapters */
178     if( EntryPoints( p_access ) )
179     {
180         msg_Warn( p_access, "could not read entry points, will not use them" );
181     }
182 
183     /* Starting title/chapter and sector */
184     if( i_title >= p_sys->i_titles )
185         i_title = 0;
186     if( (unsigned)i_chapter >= p_sys->titles[i_title].count )
187         i_chapter = 0;
188 
189     p_sys->i_sector = p_sys->p_sectors[1+i_title];
190     if( i_chapter > 0 )
191         p_sys->i_sector += p_sys->titles[i_title].seekpoints[i_chapter]
192                            / VCD_DATA_SIZE;
193 
194     /* p_access */
195     p_access->pf_read    = NULL;
196     p_access->pf_block   = Block;
197     p_access->pf_control = Control;
198     p_access->pf_seek    = Seek;
199 
200     p_sys->i_current_title = i_title;
201     p_sys->i_current_seekpoint = i_chapter;
202     p_sys->offset = (uint64_t)(p_sys->i_sector - p_sys->p_sectors[1+i_title]) *
203                                VCD_DATA_SIZE;
204 
205     return VLC_SUCCESS;
206 
207 error:
208     ioctl_Close( VLC_OBJECT(p_access), vcddev );
209     free( p_sys );
210     return VLC_EGENERIC;
211 }
212 
213 /*****************************************************************************
214  * Close: closes vcd
215  *****************************************************************************/
Close(vlc_object_t * p_this)216 static void Close( vlc_object_t *p_this )
217 {
218     stream_t     *p_access = (stream_t *)p_this;
219     access_sys_t *p_sys = p_access->p_sys;
220 
221     for( size_t i = 0; i < ARRAY_SIZE(p_sys->titles); i++ )
222         free( p_sys->titles[i].seekpoints );
223 
224     ioctl_Close( p_this, p_sys->vcddev );
225     free( p_sys );
226 }
227 
228 /*****************************************************************************
229  * Control:
230  *****************************************************************************/
Control(stream_t * p_access,int i_query,va_list args)231 static int Control( stream_t *p_access, int i_query, va_list args )
232 {
233     access_sys_t *p_sys = p_access->p_sys;
234     input_title_t ***ppp_title;
235 
236     switch( i_query )
237     {
238         /* */
239         case STREAM_CAN_SEEK:
240         case STREAM_CAN_FASTSEEK:
241         case STREAM_CAN_PAUSE:
242         case STREAM_CAN_CONTROL_PACE:
243             *va_arg( args, bool* ) = true;
244             break;
245 
246         case STREAM_GET_SIZE:
247         {
248             int i = p_sys->i_current_title;
249 
250             *va_arg( args, uint64_t * ) =
251                 (p_sys->p_sectors[i + 2] - p_sys->p_sectors[i + 1])
252                                * (uint64_t)VCD_DATA_SIZE;
253             break;
254         }
255 
256         /* */
257         case STREAM_GET_PTS_DELAY:
258             *va_arg( args, int64_t * ) = INT64_C(1000)
259                 * var_InheritInteger(p_access, "disc-caching");
260             break;
261 
262         /* */
263         case STREAM_SET_PAUSE_STATE:
264             break;
265 
266         case STREAM_GET_TITLE_INFO:
267             ppp_title = va_arg( args, input_title_t*** );
268             /* Duplicate title infos */
269             *ppp_title = vlc_alloc( p_sys->i_titles, sizeof(input_title_t *) );
270             if (!*ppp_title)
271                 return VLC_ENOMEM;
272             *va_arg( args, int* ) = p_sys->i_titles;
273             for( int i = 0; i < p_sys->i_titles; i++ )
274                 (*ppp_title)[i] = vlc_input_title_New();
275             break;
276 
277         case STREAM_GET_TITLE:
278             *va_arg( args, unsigned * ) = p_sys->i_current_title;
279             break;
280 
281         case STREAM_GET_SEEKPOINT:
282             *va_arg( args, unsigned * ) = p_sys->i_current_seekpoint;
283             break;
284 
285         case STREAM_GET_CONTENT_TYPE:
286             *va_arg( args, char ** ) = strdup("video/MP2P");
287             break;
288 
289         case STREAM_SET_TITLE:
290         {
291             int i = va_arg( args, int );
292             if( i != p_sys->i_current_title )
293             {
294                 /* Update info */
295                 p_sys->offset = 0;
296                 p_sys->i_current_title = i;
297                 p_sys->i_current_seekpoint = 0;
298 
299                 /* Next sector to read */
300                 p_sys->i_sector = p_sys->p_sectors[1+i];
301             }
302             break;
303         }
304 
305         case STREAM_SET_SEEKPOINT:
306         {
307             int i = va_arg( args, int );
308             unsigned i_title = p_sys->i_current_title;
309 
310             if( p_sys->titles[i_title].count > 0 )
311             {
312                 p_sys->i_current_seekpoint = i;
313 
314                 p_sys->i_sector = p_sys->p_sectors[1 + i_title] +
315                     p_sys->titles[i_title].seekpoints[i] / VCD_DATA_SIZE;
316 
317                 p_sys->offset = (uint64_t)(p_sys->i_sector -
318                     p_sys->p_sectors[1 + i_title]) * VCD_DATA_SIZE;
319             }
320             break;
321         }
322 
323         default:
324             return VLC_EGENERIC;
325     }
326     return VLC_SUCCESS;
327 }
328 
329 /*****************************************************************************
330  * Block:
331  *****************************************************************************/
Block(stream_t * p_access,bool * restrict eof)332 static block_t *Block( stream_t *p_access, bool *restrict eof )
333 {
334     access_sys_t *p_sys = p_access->p_sys;
335     int i_blocks = VCD_BLOCKS_ONCE;
336     block_t *p_block;
337 
338     /* Check end of title */
339     while( p_sys->i_sector >= p_sys->p_sectors[p_sys->i_current_title + 2] )
340     {
341         if( p_sys->i_current_title + 2 >= p_sys->i_titles )
342         {
343             *eof = true;
344             return NULL;
345         }
346 
347         p_sys->i_current_title++;
348         p_sys->i_current_seekpoint = 0;
349         p_sys->offset = 0;
350     }
351 
352     /* Don't read after the end of a title */
353     if( p_sys->i_sector + i_blocks >=
354         p_sys->p_sectors[p_sys->i_current_title + 2] )
355     {
356         i_blocks = p_sys->p_sectors[p_sys->i_current_title + 2 ] - p_sys->i_sector;
357     }
358 
359     /* Do the actual reading */
360     if( i_blocks < 0 || !( p_block = block_Alloc( i_blocks * VCD_DATA_SIZE ) ) )
361     {
362         msg_Err( p_access, "cannot get a new block of size: %i",
363                  i_blocks * VCD_DATA_SIZE );
364         return NULL;
365     }
366 
367     if( ioctl_ReadSectors( VLC_OBJECT(p_access), p_sys->vcddev,
368             p_sys->i_sector, p_block->p_buffer, i_blocks, VCD_TYPE ) < 0 )
369     {
370         msg_Err( p_access, "cannot read sector %i", p_sys->i_sector );
371         block_Release( p_block );
372 
373         /* Try to skip one sector (in case of bad sectors) */
374         p_sys->offset += VCD_DATA_SIZE;
375         p_sys->i_sector++;
376         return NULL;
377     }
378 
379     /* Update seekpoints */
380     for( int i_read = 0; i_read < i_blocks; i_read++ )
381     {
382         int i_title = p_sys->i_current_title;
383 
384         if( p_sys->titles[i_title].count > 0 &&
385             p_sys->i_current_seekpoint + 1 < p_sys->titles[i_title].count &&
386                 (p_sys->offset + i_read * VCD_DATA_SIZE) >=
387             p_sys->titles[i_title].seekpoints[p_sys->i_current_seekpoint + 1] )
388         {
389             msg_Dbg( p_access, "seekpoint change" );
390             p_sys->i_current_seekpoint++;
391         }
392     }
393 
394     /* Update a few values */
395     p_sys->offset += p_block->i_buffer;
396     p_sys->i_sector += i_blocks;
397 
398     return p_block;
399 }
400 
401 /*****************************************************************************
402  * Seek:
403  *****************************************************************************/
Seek(stream_t * p_access,uint64_t i_pos)404 static int Seek( stream_t *p_access, uint64_t i_pos )
405 {
406     access_sys_t *p_sys = p_access->p_sys;
407     int i_title = p_sys->i_current_title;
408     unsigned i_seekpoint;
409 
410     /* Next sector to read */
411     p_sys->offset = i_pos;
412     p_sys->i_sector = i_pos / VCD_DATA_SIZE + p_sys->p_sectors[i_title + 1];
413 
414     /* Update current seekpoint */
415     for( i_seekpoint = 0; i_seekpoint < p_sys->titles[i_title].count; i_seekpoint++ )
416     {
417         if( i_seekpoint + 1 >= p_sys->titles[i_title].count ) break;
418         if( 0 < p_sys->titles[i_title].seekpoints[i_seekpoint + 1] &&
419             i_pos < p_sys->titles[i_title].seekpoints[i_seekpoint + 1] ) break;
420     }
421 
422     if( i_seekpoint != p_sys->i_current_seekpoint )
423     {
424         msg_Dbg( p_access, "seekpoint change" );
425         p_sys->i_current_seekpoint = i_seekpoint;
426     }
427 
428     return VLC_SUCCESS;
429 }
430 
431 /*****************************************************************************
432  * EntryPoints: Reads the information about the entry points on the disc.
433  *****************************************************************************/
EntryPoints(stream_t * p_access)434 static int EntryPoints( stream_t *p_access )
435 {
436     access_sys_t *p_sys = p_access->p_sys;
437     uint8_t      sector[VCD_DATA_SIZE];
438 
439     entries_sect_t entries;
440     int i_nb;
441 
442     /* Read the entry point sector */
443     if( ioctl_ReadSectors( VLC_OBJECT(p_access), p_sys->vcddev,
444         VCD_ENTRIES_SECTOR, sector, 1, VCD_TYPE ) < 0 )
445     {
446         msg_Err( p_access, "could not read entry points sector" );
447         return VLC_EGENERIC;
448     }
449     memcpy( &entries, sector, CD_SECTOR_SIZE );
450 
451     i_nb = GetWBE( &entries.i_entries_nb );
452     if( i_nb > 500 )
453     {
454         msg_Err( p_access, "invalid entry points number" );
455         return VLC_EGENERIC;
456     }
457 
458     if( strncmp( entries.psz_id, "ENTRYVCD", sizeof( entries.psz_id ) ) &&
459         strncmp( entries.psz_id, "ENTRYSVD", sizeof( entries.psz_id ) ) )
460     {
461         msg_Err( p_access, "unrecognized entry points format" );
462         return VLC_EGENERIC;
463     }
464 
465     for( int i = 0; i < i_nb; i++ )
466     {
467         const int i_title = BCD_TO_BIN(entries.entry[i].i_track) - 2;
468         const int i_sector =
469             (MSF_TO_LBA2( BCD_TO_BIN( entries.entry[i].msf.minute ),
470                           BCD_TO_BIN( entries.entry[i].msf.second ),
471                           BCD_TO_BIN( entries.entry[i].msf.frame  ) ));
472         if( i_title < 0 ) continue;   /* Should not occur */
473         if( i_title >= p_sys->i_titles ) continue;
474 
475         msg_Dbg( p_access, "Entry[%d] title=%d sector=%d",
476                  i, i_title, i_sector );
477 
478         p_sys->titles[i_title].seekpoints = xrealloc(
479             p_sys->titles[i_title].seekpoints,
480             sizeof( uint64_t ) * (p_sys->titles[i_title].count + 1) );
481         p_sys->titles[i_title].seekpoints[p_sys->titles[i_title].count++] =
482             (i_sector - p_sys->p_sectors[i_title+1]) * VCD_DATA_SIZE;
483     }
484 
485     return VLC_SUCCESS;
486 }
487 
488