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