]> git.sesse.net Git - vlc/blob - plugins/dvd/dvd_access.c
___ _ _ _ _____ _____ ____ _ _ ____ _____
[vlc] / plugins / dvd / dvd_access.c
1 /* dvd_access.c: DVD access plugin.
2  *****************************************************************************
3  * This plugins should handle all the known specificities of the DVD format,
4  * especially the 2048 bytes logical block size.
5  * It depends on:
6  *  -libdvdcss for access and unscrambling
7  *  -dvd_ifo for ifo parsing and analyse
8  *  -dvd_udf to find files
9  *****************************************************************************
10  * Copyright (C) 1998-2001 VideoLAN
11  * $Id: dvd_access.c,v 1.19 2002/05/21 13:34:31 gbazin Exp $
12  *
13  * Author: Stéphane Borel <stef@via.ecp.fr>
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  * 
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
28  *****************************************************************************/
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <videolan/vlc.h>
38
39 #ifdef HAVE_UNISTD_H
40 #   include <unistd.h>
41 #endif
42
43 #include <fcntl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <string.h>
47 #include <errno.h>
48
49 #ifdef STRNCASECMP_IN_STRINGS_H
50 #   include <strings.h>
51 #endif
52
53 #ifdef GOD_DAMN_DMCA
54 #   include "dummy_dvdcss.h"
55 #else
56 #   include <dvdcss/dvdcss.h>
57 #endif
58
59 #include "stream_control.h"
60 #include "input_ext-intf.h"
61 #include "input_ext-dec.h"
62 #include "input_ext-plugins.h"
63
64 #include "dvd.h"
65 #include "dvd_es.h"
66 #include "dvd_seek.h"
67 #include "dvd_ifo.h"
68 #include "dvd_summary.h"
69 #include "iso_lang.h"
70
71 /*****************************************************************************
72  * Local prototypes
73  *****************************************************************************/
74
75 /* called from outside */
76 static int  DVDOpen         ( struct input_thread_s * );
77 static void DVDClose        ( struct input_thread_s * );
78 static int  DVDSetArea      ( struct input_thread_s *, struct input_area_s * );
79 static int  DVDSetProgram   ( struct input_thread_s *, pgrm_descriptor_t * );
80 static ssize_t DVDRead      ( struct input_thread_s *, byte_t *, size_t );
81 static void DVDSeek         ( struct input_thread_s *, off_t );
82
83 static char * DVDParse( input_thread_t * );
84
85 /*****************************************************************************
86  * Functions exported as capabilities. They are declared as static so that
87  * we don't pollute the namespace too much.
88  *****************************************************************************/
89 void _M( access_getfunctions)( function_list_t * p_function_list )
90 {
91 #define input p_function_list->functions.access
92     input.pf_open             = DVDOpen;
93     input.pf_close            = DVDClose;
94     input.pf_read             = DVDRead;
95     input.pf_set_area         = DVDSetArea;
96     input.pf_set_program      = DVDSetProgram;
97     input.pf_seek             = DVDSeek;
98 #undef input
99 }
100
101 /*
102  * Data access functions
103  */
104
105 #define DVDTell   LB2OFF( p_dvd->i_vts_start + p_dvd->i_vts_lb ) \
106                   - p_input->stream.p_selected_area->i_start
107
108 /*****************************************************************************
109  * DVDOpen: open dvd
110  *****************************************************************************/
111 static int DVDOpen( struct input_thread_s *p_input )
112 {
113     char *               psz_device;
114     thread_dvd_data_t *  p_dvd;
115     input_area_t *       p_area;
116     int                  i;
117
118     p_dvd = malloc( sizeof(thread_dvd_data_t) );
119     if( p_dvd == NULL )
120     {
121         intf_ErrMsg( "dvd error: out of memory" );
122         return -1;
123     }
124     p_input->p_access_data = (void *)p_dvd;
125     
126     /* Parse command line */
127     if( !( psz_device = DVDParse( p_input ) ) )
128     {
129         free( p_dvd );
130         return -1;
131     }
132     
133     /* 
134      * set up input
135      */ 
136     p_input->i_mtu = 0;
137
138     /*
139      *  get plugin ready
140      */ 
141     p_dvd->dvdhandle = dvdcss_open( psz_device );
142     
143     /* free allocated string */
144     free( psz_device );
145
146     if( p_dvd->dvdhandle == NULL )
147     {
148         intf_ErrMsg( "dvd error: dvdcss can't open device" );
149         free( p_dvd );
150         return -1;
151     }
152
153     if( dvdcss_seek( p_dvd->dvdhandle, 0, DVDCSS_NOFLAGS ) < 0 )
154     {
155         intf_ErrMsg( "dvd error: %s", dvdcss_error( p_dvd->dvdhandle ) );
156         dvdcss_close( p_dvd->dvdhandle );
157         free( p_dvd );
158         return -1;
159     }
160
161     /* Ifo allocation & initialisation */
162     if( IfoCreate( p_dvd ) < 0 )
163     {
164         intf_ErrMsg( "dvd error: allcation error in ifo" );
165         dvdcss_close( p_dvd->dvdhandle );
166         free( p_dvd );
167         return -1;
168     }
169
170     if( IfoInit( p_dvd->p_ifo ) < 0 )
171     {
172         intf_ErrMsg( "dvd error: fatal failure in ifo" );
173         IfoDestroy( p_dvd->p_ifo );
174         dvdcss_close( p_dvd->dvdhandle );
175         free( p_dvd );
176         return -1;
177     }
178
179     /* Set stream and area data */
180     vlc_mutex_lock( &p_input->stream.stream_lock );
181
182     p_input->stream.i_method = INPUT_METHOD_DVD;
183     p_input->stream.b_pace_control = 1;
184     p_input->stream.b_seekable = 1;
185     p_input->stream.p_selected_area->i_size = 0;
186     p_input->stream.p_selected_area->i_tell = 0;
187
188     /* Initialize ES structures */
189     input_InitStream( p_input, sizeof( stream_ps_data_t ) );
190
191 #define title_inf p_dvd->p_ifo->vmg.title_inf
192     intf_WarnMsg( 3, "dvd info: number of titles: %d", title_inf.i_title_nb );
193
194 #define area p_input->stream.pp_areas
195     /* We start from 1 here since the default area 0
196      * is reserved for video_ts.vob */
197     for( i = 1 ; i <= title_inf.i_title_nb ; i++ )
198     {
199         input_AddArea( p_input );
200
201         /* Titles are Program Chains */
202         area[i]->i_id = i;
203
204         /* Absolute start offset and size 
205          * We can only set that with vts ifo, so we do it during the
206          * first call to DVDSetArea */
207         area[i]->i_start = 0;
208         area[i]->i_size = 0;
209
210         /* Number of chapters */
211         area[i]->i_part_nb = title_inf.p_attr[i-1].i_chapter_nb;
212         area[i]->i_part = 1;
213
214         /* Offset to vts_i_0.ifo */
215         area[i]->i_plugin_data = p_dvd->p_ifo->i_start +
216                        title_inf.p_attr[i-1].i_start_sector;
217     }   
218 #undef area
219     
220     p_dvd->i_title = p_dvd->i_title <= title_inf.i_title_nb ?
221                      p_dvd->i_title : 1;
222 #undef title_inf
223
224     p_area = p_input->stream.pp_areas[p_dvd->i_title];
225     
226     p_area->i_part = p_dvd->i_chapter <= p_area->i_part_nb ?
227                      p_dvd->i_chapter : 1;
228     p_dvd->i_chapter = 1;
229     
230     p_dvd->b_new_chapter = 0;
231     p_dvd->i_audio_nb = 0;
232     p_dvd->i_spu_nb = 0;
233     
234     /* set title, chapter, audio and subpic */
235     if( DVDSetArea( p_input, p_area ) < 0 )
236     {
237         vlc_mutex_unlock( &p_input->stream.stream_lock );
238         IfoDestroy( p_dvd->p_ifo );
239         dvdcss_close( p_dvd->dvdhandle );
240         free( p_dvd );
241         return -1;
242     }
243
244     vlc_mutex_unlock( &p_input->stream.stream_lock );
245
246     p_input->psz_demux = "dvd";
247
248     return 0;
249 }
250
251 /*****************************************************************************
252  * DVDClose: close dvd
253  *****************************************************************************/
254 static void DVDClose( struct input_thread_s *p_input )
255 {
256     thread_dvd_data_t *p_dvd = (thread_dvd_data_t*)p_input->p_access_data;
257
258     IfoDestroy( p_dvd->p_ifo );
259     dvdcss_close( p_dvd->dvdhandle );
260     free( p_dvd );
261 }
262
263 /*****************************************************************************
264  * DVDSetProgram: used to change angle
265  *****************************************************************************/
266 static int DVDSetProgram( input_thread_t    * p_input,
267                           pgrm_descriptor_t * p_program ) 
268 {
269     if( p_input->stream.p_selected_program != p_program )
270     {
271         thread_dvd_data_t *  p_dvd;
272         int                  i_angle;
273     
274         p_dvd   = (thread_dvd_data_t*)(p_input->p_access_data);
275         i_angle = p_program->i_number;
276
277         /* DVD is actually mono-program: we only need the current angle
278          * number, so copy the data between programs */
279         memcpy( p_program,
280                 p_input->stream.p_selected_program,
281                 sizeof(pgrm_descriptor_t) );
282         p_program->i_number                = i_angle;
283         p_input->stream.p_selected_program = p_program;
284
285 #define title \
286     p_dvd->p_ifo->vts.title_unit.p_title[p_dvd->i_title_id-1].title
287         if( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 )
288         {
289             if( ( p_program->i_number - p_dvd->i_angle ) < 0 )
290             {
291                 /* we have to go backwards */
292                 p_dvd->i_map_cell = 0;
293             }
294             p_dvd->i_prg_cell += ( p_program->i_number - p_dvd->i_angle );
295             p_dvd->i_map_cell =  CellPrg2Map( p_dvd );
296             p_dvd->i_map_cell += p_dvd->i_angle_cell;
297             p_dvd->i_vts_lb   =  CellFirstSector( p_dvd );
298             p_dvd->i_last_lb  =  CellLastSector( p_dvd );
299             p_dvd->i_angle    =  p_program->i_number;
300         }
301         else
302         {
303             p_dvd->i_angle    =  p_program->i_number;
304         }
305 #undef title
306         intf_WarnMsg( 3, "dvd info: angle %d selected", p_dvd->i_angle );
307     }
308
309     return 0;
310 }
311
312 /*****************************************************************************
313  * DVDSetArea: initialize input data for title x, chapter y.
314  * It should be called for each user navigation request.
315  *****************************************************************************
316  * Take care that i_title starts from 0 (vmg) and i_chapter start from 1.
317  * Note that you have to take the lock before entering here.
318  *****************************************************************************/
319 #define vmg p_dvd->p_ifo->vmg
320 #define vts p_dvd->p_ifo->vts
321
322 static void DVDFlushStream( input_thread_t * p_input )
323 {
324     if( p_input->stream.pp_programs != NULL )
325     {
326         /* We don't use input_EndStream here since
327          * we keep area structures */
328         while( p_input->stream.i_es_number )
329         {
330             input_DelES( p_input, p_input->stream.pp_es[0] );
331         }
332         
333         while( p_input->stream.i_pgrm_number )
334         {
335             input_DelProgram( p_input, p_input->stream.pp_programs[0] );
336         }
337
338         if( p_input->stream.pp_selected_es )
339         {
340             free( p_input->stream.pp_selected_es );
341             p_input->stream.pp_selected_es = NULL;
342         }
343         p_input->stream.i_selected_es_number = 0;
344     }
345
346     return;
347 }
348
349 static int DVDReadAngle( input_thread_t * p_input )
350 {
351     thread_dvd_data_t * p_dvd;
352     int                 i_angle_nb;
353     int                 i;
354
355     p_dvd      = (thread_dvd_data_t*)(p_input->p_access_data);
356     i_angle_nb = vmg.title_inf.p_attr[p_dvd->i_title-1].i_angle_nb;
357     
358     input_AddProgram( p_input, 1, sizeof( stream_ps_data_t ) );
359     p_input->stream.p_selected_program = p_input->stream.pp_programs[0];
360
361     for( i = 1 ; i < i_angle_nb ; i++ )
362     {
363         input_AddProgram( p_input, i+1, 0 );
364     }
365
366     return i_angle_nb;
367 }
368
369 static int DVDSetArea( input_thread_t * p_input, input_area_t * p_area )
370 {
371     thread_dvd_data_t *  p_dvd;
372
373     p_dvd = (thread_dvd_data_t*)(p_input->p_access_data);
374
375     /* we can't use the interface slider until initilization is complete */
376     p_input->stream.b_seekable = 0;
377
378     if( p_area != p_input->stream.p_selected_area )
379     {
380         int     i_vts_title;
381         u32     i_first;
382         u32     i_last;
383
384         /* Reset the Chapter position of the old title */
385         p_input->stream.p_selected_area->i_part = 1;
386         p_input->stream.p_selected_area         = p_area;
387
388         /*
389          *  We have to load all title information
390          */
391
392         /* title number as it appears in the interface list */
393         p_dvd->i_title      = p_area->i_id;
394         p_dvd->i_chapter_nb = p_area->i_part_nb;
395
396         if( IfoTitleSet( p_dvd->p_ifo, p_dvd->i_title ) < 0 )
397         {
398             intf_ErrMsg( "dvd error: fatal error in vts ifo" );
399             free( p_dvd );
400             return -1;
401         }
402
403         /* title position inside the selected vts */
404         i_vts_title       = vmg.title_inf.p_attr[p_dvd->i_title-1].i_title_num;
405         p_dvd->i_title_id =
406             vts.title_inf.p_title_start[i_vts_title-1].i_title_id;
407
408         intf_WarnMsg( 3, "dvd: title %d vts_title %d pgc %d",
409                       p_dvd->i_title, i_vts_title, p_dvd->i_title_id );
410
411         /* title set offset XXX: convert to block values */
412         p_dvd->i_vts_start =
413             vts.i_pos + vts.manager_inf.i_title_vob_start_sector;
414
415         /* last cell */
416         p_dvd->i_prg_cell = -1 +
417             vts.title_unit.p_title[p_dvd->i_title_id-1].title.i_cell_nb;
418         p_dvd->i_map_cell = 0;
419         p_dvd->i_map_cell = CellPrg2Map( p_dvd );
420         i_last            = CellLastSector( p_dvd );
421
422         /* first cell */
423         p_dvd->i_prg_cell   = 0;
424         p_dvd->i_map_cell   = 0;
425         p_dvd->i_angle_cell = 0;
426         p_dvd->i_map_cell   = CellPrg2Map    ( p_dvd );
427         p_dvd->i_vts_lb     = CellFirstSector( p_dvd );
428         p_dvd->i_last_lb    = CellLastSector ( p_dvd );
429
430         /* Force libdvdcss to check its title key.
431          * It is only useful for title cracking method. Methods using the
432          * decrypted disc key are fast enough to check the key at each seek */
433         i_first = dvdcss_seek( p_dvd->dvdhandle,
434                                p_dvd->i_vts_start + p_dvd->i_vts_lb,
435                                DVDCSS_SEEK_KEY );
436         if( i_first < 0 )
437         {
438             intf_ErrMsg( "dvd error: %s", dvdcss_error( p_dvd->dvdhandle ) );
439             return -1;
440         }
441
442         /* Area definition */
443         p_input->stream.p_selected_area->i_start = LB2OFF( i_first );
444         p_input->stream.p_selected_area->i_size  =
445                                         LB2OFF( i_last + 1 - p_dvd->i_vts_lb );
446
447         /* Destroy obsolete ES by reinitializing programs */
448         DVDFlushStream( p_input );
449         
450         /* Angle management: angles are handled through programs */
451         p_dvd->i_angle_nb = DVDReadAngle( p_input );
452         if( ( p_dvd->i_angle <= 0 ) || p_dvd->i_angle > p_dvd->i_angle_nb )
453         {
454             p_dvd->i_angle = 1;
455         }
456        
457         DVDSetProgram( p_input,
458                        p_input->stream.pp_programs[p_dvd->i_angle-1] ); 
459
460         intf_WarnMsg( 3, "dvd info: title first %i, last %i, size %i",
461                          i_first, i_last, i_last + 1 - p_dvd->i_vts_lb );
462         IfoPrintTitle( p_dvd );
463
464         /* No PSM to read in DVD mode, we already have all information */
465         p_input->stream.p_selected_program->b_is_ok = 1;
466
467         /* Find all ES in title with ifo data */
468         DVDReadVideo( p_input );
469         DVDReadAudio( p_input );
470         DVDReadSPU  ( p_input );
471    
472         if( p_input->p_demux_module )
473         {
474             DVDLaunchDecoders( p_input );
475         }
476
477     } /* i_title >= 0 */
478     else
479     {
480         p_area = p_input->stream.p_selected_area;
481     }
482
483     /* Chapter selection */
484     p_dvd->i_chapter = DVDSetChapter( p_dvd, p_area->i_part );
485     
486     p_input->stream.p_selected_area->i_tell = DVDTell;
487
488     /* warn interface that something has changed */
489     p_input->stream.b_seekable = 1;
490     p_input->stream.b_changed  = 1;
491
492     return 0;
493 }
494 #undef vts
495 #undef vmg
496
497 #define title \
498     p_dvd->p_ifo->vts.title_unit.p_title[p_dvd->i_title_id-1].title
499     
500 /*****************************************************************************
501  * DVDRead: reads data packets.
502  *****************************************************************************
503  * Returns -1 in case of error, 0 in case of EOF, otherwise the number of
504  * bytes.
505  *****************************************************************************/
506 static ssize_t DVDRead( input_thread_t * p_input,
507                         byte_t * p_buffer, size_t i_count )
508 {
509     thread_dvd_data_t *     p_dvd;
510     int                     i_read;
511     int                     i_blocks;
512     int                     i_block_once = 0;
513
514     p_dvd = (thread_dvd_data_t *)(p_input->p_access_data);
515
516     i_read = 0;
517     i_blocks = OFF2LB(i_count);
518
519     while( i_blocks )
520     {
521         i_block_once = LbMaxOnce( p_dvd );
522         if( i_block_once > i_blocks )
523         {
524             i_block_once = i_blocks;
525         }
526         else if( i_block_once <= 0 )
527         {
528             /* EOT */
529             break;
530         }
531
532         if( i_block_once != dvdcss_read( p_dvd->dvdhandle, p_buffer,
533                                          i_block_once, DVDCSS_READ_DECRYPT ) )
534         {
535             return -1;
536         }
537
538         i_blocks -= i_block_once;
539         i_read += i_block_once;
540         p_buffer += LB2OFF( i_block_once );
541
542         /* Update global position */
543         p_dvd->i_vts_lb += i_block_once;
544     }
545
546     vlc_mutex_lock( &p_input->stream.stream_lock );
547
548     p_input->stream.p_selected_area->i_tell += LB2OFF( i_read );
549     if( p_dvd->b_new_chapter )
550     {
551         p_input->stream.p_selected_area->i_part = p_dvd->i_chapter;
552         p_dvd->b_new_chapter                    = 0;
553     }
554
555     if( ( p_input->stream.p_selected_area->i_tell
556             >= p_input->stream.p_selected_area->i_size )
557        || ( i_block_once  <= 0 ) )
558     {
559         if( ( p_dvd->i_title + 1 ) >= p_input->stream.i_area_nb )
560         {
561             /* EOF */
562             vlc_mutex_unlock( &p_input->stream.stream_lock );
563             return 0;
564         }
565
566         /* EOT */
567         intf_WarnMsg( 4, "dvd info: new title" );
568         p_dvd->i_title++;
569         DVDSetArea( p_input, p_input->stream.pp_areas[p_dvd->i_title] );
570     }
571
572     vlc_mutex_unlock( &p_input->stream.stream_lock );
573
574     return LB2OFF( i_read );
575 }
576
577 /*****************************************************************************
578  * DVDSeek : Goes to a given position on the stream.
579  *****************************************************************************
580  * This one is used by the input and translate chronological position from
581  * input to logical position on the device.
582  * The lock should be taken before calling this function.
583  *****************************************************************************/
584 static void DVDSeek( input_thread_t * p_input, off_t i_off )
585 {
586     thread_dvd_data_t *     p_dvd;
587     
588     p_dvd = ( thread_dvd_data_t * )(p_input->p_access_data);
589
590     vlc_mutex_lock( &p_input->stream.stream_lock );
591     p_dvd->i_vts_lb = OFF2LB(i_off + p_input->stream.p_selected_area->i_start)
592                        - p_dvd->i_vts_start;
593     vlc_mutex_unlock( &p_input->stream.stream_lock );
594
595     p_dvd->i_prg_cell = Lb2CellPrg( p_dvd );
596     p_dvd->i_map_cell = Lb2CellMap( p_dvd );
597     
598     if( CellIsInterleaved( p_dvd ) )
599     {
600         /* if we're inside a multi-angle zone, we have to choose i_sector
601          * in the current angle ; we can't do it all the time since cells
602          * can be very wide out of such zones */
603         p_dvd->i_vts_lb = CellFirstSector( p_dvd );
604     }
605     
606     p_dvd->i_last_lb  = CellLastSector( p_dvd );
607     p_dvd->i_chapter  = CellPrg2Chapter( p_dvd );
608
609     if( dvdcss_seek( p_dvd->dvdhandle, p_dvd->i_vts_start + p_dvd->i_vts_lb,
610                      DVDCSS_SEEK_MPEG ) < 0 )
611     {
612         intf_ErrMsg( "dvd error: %s", dvdcss_error( p_dvd->dvdhandle ) );
613         p_input->b_error = 1;
614         return;
615     }
616
617     vlc_mutex_lock( &p_input->stream.stream_lock );
618     p_input->stream.p_selected_area->i_part = p_dvd->i_chapter;
619     p_input->stream.p_selected_area->i_tell = DVDTell;
620     vlc_mutex_unlock( &p_input->stream.stream_lock );
621
622     intf_WarnMsg( 4, "Program Cell: %d Cell: %d Chapter: %d tell %lld",
623                      p_dvd->i_prg_cell, p_dvd->i_map_cell, p_dvd->i_chapter, DVDTell );
624
625     return;
626 }
627
628 /*****************************************************************************
629  * DVDParse: parse command line
630  *****************************************************************************/
631 static char * DVDParse( input_thread_t * p_input )
632 {
633     thread_dvd_data_t *  p_dvd;
634     struct stat          stat_info;
635     char *               psz_parser;
636     char *               psz_device;
637     char *               psz_raw;
638     char *               psz_next;
639     boolean_t            b_options = 0;
640     int                  i_title = 1;
641     int                  i_chapter = 1;
642     int                  i_angle = 1;
643     int                  i;
644
645     p_dvd = (thread_dvd_data_t*)(p_input->p_access_data);
646
647 #ifdef WIN32
648     /* On Win32 we want the DVD access plugin to be explicitly requested,
649      * we end up with lots of problems otherwise */
650     if( !p_input->psz_access || !*p_input->psz_access ) return NULL;
651 #endif
652
653     psz_parser = psz_device = strdup( p_input->psz_name );
654     if( !psz_parser )
655     {
656         return NULL;
657     }
658
659     /* Parse input string :
660      * [device][@rawdevice][@[title][,[chapter][,angle]]] */
661     while( *psz_parser && *psz_parser != '@' )
662     {
663         psz_parser++;
664     }
665
666     if( *psz_parser == '@' )
667     {
668         /* Maybe found raw device or option list */
669         *psz_parser = '\0';
670         psz_raw = ++psz_parser;
671     }
672     else
673     {
674         psz_raw = "";
675     }
676
677     if( *psz_parser && !strtol( psz_parser, NULL, 10 ) )
678     {
679         /* what we've found is either a raw device or a partial option
680          * list e.g. @,29 or both a device and a list ; search end of string */
681         while( *psz_parser && *psz_parser != '@' )
682         {
683             psz_parser++;
684         }
685         
686         if( *psz_parser == '@' )
687         {
688             /* found end of raw device, and beginning of options */
689             *psz_parser = '\0';
690             ++psz_parser;
691             b_options = 1;
692         }
693         else
694         {
695             psz_parser = psz_raw + 1;
696             for( i=0 ; i<3 ; i++ )
697             {
698                 if( !*psz_parser )
699                 {
700                     /* we have only a raw device */
701                     break;
702                 }
703                 if( strtol( psz_parser, NULL, 10 ) )
704                 {
705                     /* we have only a partial list of options, no device */
706                     psz_parser = psz_raw;
707                     psz_raw = "";
708                     b_options = 1;
709                     break;
710                 }
711                 psz_parser++;
712             }
713         }
714     }
715     else
716     {
717         /* found beginning of options ; no raw device specified */
718         psz_raw = "";
719         b_options = 1;
720     }
721
722     if( b_options )
723     {
724         /* Found options */
725         i_title = (int)strtol( psz_parser, &psz_next, 10 );
726         if( *psz_next )
727         {
728             psz_parser = psz_next + 1;
729             i_chapter = (int)strtol( psz_parser, &psz_next, 10 );
730             if( *psz_next )
731             {
732                 i_angle = (int)strtol( psz_next + 1, NULL, 10 );
733             }
734         }
735
736         p_dvd->i_title = i_title ? i_title : 1;
737         p_dvd->i_chapter = i_chapter ? i_chapter : 1;
738         p_dvd->i_angle = i_angle ? i_angle : 1;
739     }
740
741     if( *psz_raw )
742     {
743         if( *psz_raw )
744         {
745             /* check the raw device */
746             if( stat( psz_raw, &stat_info ) == -1 )
747             {
748                 intf_WarnMsg( 3, "dvd warning: cannot stat() raw"
749                                 " device `%s' (%s)",
750                              psz_raw, strerror(errno));
751                 /* put back '@' */
752                 *(psz_raw - 1) = '@';
753                 psz_raw = "";
754             }
755             else
756             {
757                 char * psz_env;
758                 
759 #ifndef WIN32    
760                 if( !S_ISCHR(stat_info.st_mode) )
761                 {
762                     intf_WarnMsg( 3, "dvd warning: raw device %s is"
763                                      " not a valid char device", psz_raw );
764                     /* put back '@' */
765                     *(psz_raw - 1) = '@';
766                     psz_raw = "";
767                 }
768                 else
769 #endif
770                 {
771                     psz_env = malloc( strlen("DVDCSS_RAW_DEVICE=")
772                                     + strlen( psz_raw ) + 1 );
773                     sprintf( psz_env, "DVDCSS_RAW_DEVICE=%s", psz_raw );
774                     putenv( psz_env );
775                 }
776             }
777         }
778         else
779         {
780             psz_raw = "";
781         }
782     }
783     
784     if( !*psz_device )
785     {
786         free( psz_device );
787         
788         if( !p_input->psz_access )
789         {
790             /* no device and no access specified: we probably don't want DVD */
791             return NULL;
792         }
793         psz_device = config_GetPszVariable( "dvd" );
794     }
795
796 #ifndef WIN32    
797     /* check block device */
798     if( stat( psz_device, &stat_info ) == -1 )
799     {
800         intf_ErrMsg( "dvd error: cannot stat() device `%s' (%s)",
801                      psz_device, strerror(errno));
802         free( psz_device );
803         return NULL;                    
804     }
805     
806     if( !S_ISBLK(stat_info.st_mode) && !S_ISCHR(stat_info.st_mode) )
807     {
808         intf_WarnMsg( 3, "input: DVD plugin discarded"
809                          " (not a valid block device)" );
810         free( psz_device );
811         return NULL;
812     }
813 #endif
814     
815     intf_WarnMsg( 2, "input: dvd=%s raw=%s title=%d chapter=%d angle=%d",
816                   psz_device, psz_raw, p_dvd->i_title,
817                   p_dvd->i_chapter, p_dvd->i_angle );
818
819     return psz_device;
820