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