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