X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=plugins%2Fdvd%2Finput_dvd.c;h=af0467adfcf8548746330069e543cb47a2444647;hb=440f9992ee947ea5fd0debbf35fdd1011c6404b3;hp=c6322a1d67f826fd5ad3bc685fa05e3af09d2227;hpb=c3e2bf1d3bb2c79201f80b26acb3d787156918d7;p=vlc diff --git a/plugins/dvd/input_dvd.c b/plugins/dvd/input_dvd.c index c6322a1d67..af0467adfc 100644 --- a/plugins/dvd/input_dvd.c +++ b/plugins/dvd/input_dvd.c @@ -1,6 +1,6 @@ /***************************************************************************** * input_dvd.c: DVD raw reading plugin. - * --- + ***************************************************************************** * This plugins should handle all the known specificities of the DVD format, * especially the 2048 bytes logical block size. * It depends on: @@ -10,7 +10,7 @@ * -dvd_udf to find files ***************************************************************************** * Copyright (C) 1998-2001 VideoLAN - * $Id: input_dvd.c,v 1.7 2001/02/12 07:52:40 sam Exp $ + * $Id: input_dvd.c,v 1.61 2001/05/30 17:03:12 sam Exp $ * * Author: Stéphane Borel * @@ -34,6 +34,13 @@ *****************************************************************************/ #include "defs.h" +#ifdef HAVE_CSS +#define MODULE_NAME dvd +#else /* HAVE_CSS */ +#define MODULE_NAME dvdnocss +#endif /* HAVE_CSS */ +#include "modules_inner.h" + #include #include #include @@ -44,8 +51,10 @@ #include #include +#ifdef STRNCASECMP_IN_STRINGS_H +# include +#endif #include -#include #include "config.h" #include "common.h" @@ -62,10 +71,11 @@ #include "input_ext-dec.h" #include "input.h" -#include "input_netlist.h" +#include "dvd_netlist.h" #include "dvd_ifo.h" #include "dvd_css.h" +#include "dvd_summary.h" #include "input_dvd.h" #include "mpeg_system.h" @@ -76,34 +86,40 @@ /***************************************************************************** * Local prototypes *****************************************************************************/ +/* called from outside */ static int DVDProbe ( probedata_t *p_data ); -static int DVDCheckCSS ( struct input_thread_s * ); -static int DVDRead ( struct input_thread_s *, data_packet_t ** ); static void DVDInit ( struct input_thread_s * ); -static void DVDOpen ( struct input_thread_s * ); -static void DVDClose ( struct input_thread_s * ); static void DVDEnd ( struct input_thread_s * ); +static int DVDSetArea ( struct input_thread_s *, struct input_area_s * ); +static int DVDRead ( struct input_thread_s *, data_packet_t ** ); static void DVDSeek ( struct input_thread_s *, off_t ); static int DVDRewind ( struct input_thread_s * ); +/* called only inside */ +static int DVDChooseAngle( thread_dvd_data_t * ); +static int DVDFindCell( thread_dvd_data_t * ); +static int DVDFindSector( thread_dvd_data_t * ); +static int DVDChapterSelect( thread_dvd_data_t *, int ); + /***************************************************************************** * Functions exported as capabilities. They are declared as static so that * we don't pollute the namespace too much. *****************************************************************************/ -void input_getfunctions( function_list_t * p_function_list ) +void _M( input_getfunctions )( function_list_t * p_function_list ) { #define input p_function_list->functions.input p_function_list->pf_probe = DVDProbe; input.pf_init = DVDInit; - input.pf_open = DVDOpen; - input.pf_close = DVDClose; + input.pf_open = NULL; /* Set in DVDInit */ + input.pf_close = NULL; input.pf_end = DVDEnd; input.pf_read = DVDRead; + input.pf_set_area = DVDSetArea; input.pf_demux = input_DemuxPS; - input.pf_new_packet = input_NetlistNewPacket; - input.pf_new_pes = input_NetlistNewPES; - input.pf_delete_packet = input_NetlistDeletePacket; - input.pf_delete_pes = input_NetlistDeletePES; + input.pf_new_packet = DVDNewPacket; + input.pf_new_pes = DVDNewPES; + input.pf_delete_packet = DVDDeletePacket; + input.pf_delete_pes = DVDDeletePES; input.pf_rewind = DVDRewind; input.pf_seek = DVDSeek; #undef input @@ -118,27 +134,40 @@ void input_getfunctions( function_list_t * p_function_list ) *****************************************************************************/ static int DVDProbe( probedata_t *p_data ) { + input_thread_t * p_input = (input_thread_t *)p_data; + + char * psz_name = p_input->p_source; + int i_handle; + int i_score = 5; + if( TestMethod( INPUT_METHOD_VAR, "dvd" ) ) { +#ifdef HAVE_CSS return( 999 ); +#else /* HAVE_CSS */ + return( 998 ); +#endif /* HAVE_CSS */ } - return 5; -} + if( ( strlen(psz_name) > 4 ) && !strncasecmp( psz_name, "dvd:", 4 ) ) + { + /* If the user specified "dvd:" then it's probably a DVD */ +#ifdef HAVE_CSS + i_score = 100; +#else /* HAVE_CSS */ + i_score = 90; +#endif /* HAVE_CSS */ + psz_name += 4; + } -/***************************************************************************** - * DVDCheckCSS: check the stream - *****************************************************************************/ -static int DVDCheckCSS( input_thread_t * p_input ) -{ -#if defined( HAVE_SYS_DVDIO_H ) || defined( LINUX_DVD ) - return CSSTest( p_input->i_handle ); -#else - /* DVD ioctls unavailable. - * FIXME: Check the stream to see whether it is encrypted or not - * to give and accurate error message */ - return 0; -#endif + i_handle = open( psz_name, 0 ); + if( i_handle == -1 ) + { + return( 0 ); + } + close( i_handle ); + + return( i_score ); } /***************************************************************************** @@ -146,253 +175,607 @@ static int DVDCheckCSS( input_thread_t * p_input ) *****************************************************************************/ static void DVDInit( input_thread_t * p_input ) { - thread_dvd_data_t * p_method; - off_t i_start; - - if( (p_method = malloc( sizeof(thread_dvd_data_t) )) == NULL ) + thread_dvd_data_t * p_dvd; + input_area_t * p_area; + int i_title; + int i_chapter; + int i; + + p_dvd = malloc( sizeof(thread_dvd_data_t) ); + if( p_dvd == NULL ) { - intf_ErrMsg( "Out of memory" ); + intf_ErrMsg( "dvd error: out of memory" ); p_input->b_error = 1; return; } - p_input->p_plugin_data = (void *)p_method; + p_input->p_plugin_data = (void *)p_dvd; p_input->p_method_data = NULL; - p_method->i_fd = p_input->i_handle; - /* FIXME: read several packets once */ - p_method->i_read_once = 1; - p_method->i_title = 0; + /* Set callback */ + p_input->pf_open = p_input->pf_file_open; + p_input->pf_close = p_input->pf_file_close; + + p_dvd->i_fd = p_input->i_handle; + + /* reading several block once seems to cause lock-up + * when using input_ToggleES + * who wrote thez damn buggy piece of shit ??? --stef */ + p_dvd->i_block_once = 32; + p_input->i_read_once = 128; + i = CSSTest( p_input->i_handle ); + + if( i < 0 ) + { + intf_ErrMsg( "dvd error: error in css" ); + free( p_dvd ); + p_input->b_error = 1; + return; + } + + p_dvd->b_encrypted = i; lseek( p_input->i_handle, 0, SEEK_SET ); /* Reading structures initialisation */ - input_NetlistInit( p_input, 4096, 4096, DVD_LB_SIZE, - p_method->i_read_once ); + p_input->p_method_data = + DVDNetlistInit( 2048, 4096, 2048, DVD_LB_SIZE, p_dvd->i_block_once ); + intf_WarnMsg( 2, "dvd info: netlist initialized" ); - /* Ifo initialisation */ - p_method->ifo = IfoInit( p_input->i_handle ); - IfoRead( &(p_method->ifo) ); - intf_Msg( "Ifo: Initialized" ); + /* Ifo allocation & initialisation */ + if( IfoCreate( p_dvd ) < 0 ) + { + intf_ErrMsg( "dvd error: allcation error in ifo" ); + p_input->b_error = 1; + return; + } - /* CSS authentication and keys */ - if( ( p_method->b_encrypted = DVDCheckCSS( p_input ) ) ) + if( IfoInit( p_dvd->p_ifo ) < 0 ) { -#if defined( HAVE_SYS_DVDIO_H ) || defined( LINUX_DVD ) - int i; + intf_ErrMsg( "dvd error: fatal failure in ifo" ); + free( p_dvd ); + p_input->b_error = 1; + return; + } - p_method->css = CSSInit( p_input->i_handle ); - p_method->css.i_title_nb = p_method->ifo.vmg.mat.i_tts_nb; - if( (p_method->css.p_title_key = - malloc( p_method->css.i_title_nb *sizeof(title_key_t) ) ) == NULL ) + /* CSS initialisation */ + if( p_dvd->b_encrypted ) + { + p_dvd->p_css = malloc( sizeof(css_t) ); + if( p_dvd->p_css == NULL ) { - intf_ErrMsg( "Out of memory" ); + intf_ErrMsg( "dvd error: couldn't create css structure" ); + free( p_dvd ); p_input->b_error = 1; return; } - for( i=0 ; icss.i_title_nb ; i++ ) + + p_dvd->p_css->i_agid = 0; + + if( CSSInit( p_input->i_handle, p_dvd->p_css ) < 0 ) { - p_method->css.p_title_key[i].i = - p_method->ifo.p_vts[i].i_pos + - p_method->ifo.p_vts[i].mat.i_tt_vobs_ssector *DVD_LB_SIZE; + intf_ErrMsg( "dvd error: fatal failure in css" ); + free( p_dvd->p_css ); + free( p_dvd ); + p_input->b_error = 1; + return; } - CSSGetKeys( &(p_method->css) ); - intf_Msg( "CSS: Initialized" ); -#else - intf_ErrMsg( "Unscrambling not supported" ); -#endif - } - /* FIXME: Kludge beginning of vts_01_1.vob */ - i_start = p_method->ifo.p_vts[0].i_pos + - p_method->ifo.p_vts[0].mat.i_tt_vobs_ssector *DVD_LB_SIZE; + intf_WarnMsg( 2, "dvd info: css initialized" ); + } - i_start = lseek( p_input->i_handle, i_start, SEEK_SET ); - intf_Msg( "VOB start at : %lld", (long long)i_start ); + /* Set stream and area data */ + vlc_mutex_lock( &p_input->stream.stream_lock ); /* Initialize ES structures */ input_InitStream( p_input, sizeof( stream_ps_data_t ) ); - input_AddProgram( p_input, 0, sizeof( stream_ps_data_t ) ); - if( p_input->stream.b_seekable ) + /* disc input method */ + p_input->stream.i_method = INPUT_METHOD_DVD; + +#define title_inf p_dvd->p_ifo->vmg.title_inf + intf_WarnMsg( 2, "dvd info: number of titles: %d", title_inf.i_title_nb ); + +#define area p_input->stream.pp_areas + /* We start from 1 here since the default area 0 + * is reserved for video_ts.vob */ + for( i = 1 ; i <= title_inf.i_title_nb ; i++ ) + { + input_AddArea( p_input ); + + /* Titles are Program Chains */ + area[i]->i_id = i; + + /* Absolute start offset and size + * We can only set that with vts ifo, so we do it during the + * first call to DVDSetArea */ + area[i]->i_start = 0; + area[i]->i_size = 0; + + /* Number of chapters */ + area[i]->i_part_nb = title_inf.p_attr[i-1].i_chapter_nb; + area[i]->i_part = 1; + + /* Number of angles */ + area[i]->i_angle_nb = 0; + area[i]->i_angle = 1; + + /* Offset to vts_i_0.ifo */ + area[i]->i_plugin_data = p_dvd->p_ifo->i_off + + ( title_inf.p_attr[i-1].i_start_sector * DVD_LB_SIZE ); + } +#undef area + + /* Get requested title - if none try the first title */ + i_title = main_GetIntVariable( INPUT_TITLE_VAR, 1 ); + if( i_title <= 0 || i_title > title_inf.i_title_nb ) + { + i_title = 1; + } + +#undef title_inf + + /* Get requested chapter - if none defaults to first one */ + i_chapter = main_GetIntVariable( INPUT_CHAPTER_VAR, 1 ); + if( i_chapter <= 0 ) { - stream_ps_data_t * p_demux_data = - (stream_ps_data_t *)p_input->stream.pp_programs[0]->p_demux_data; + i_chapter = 1; + } + + p_input->stream.pp_areas[i_title]->i_part = i_chapter; + + p_area = p_input->stream.pp_areas[i_title]; + + /* set title, chapter, audio and subpic */ + DVDSetArea( p_input, p_area ); + + vlc_mutex_unlock( &p_input->stream.stream_lock ); + + return; +} + +/***************************************************************************** + * DVDEnd: frees unused data + *****************************************************************************/ +static void DVDEnd( input_thread_t * p_input ) +{ + thread_dvd_data_t * p_dvd; + dvd_netlist_t * p_netlist; - /* Pre-parse the stream to gather stream_descriptor_t. */ - p_input->stream.pp_programs[0]->b_is_ok = 0; - p_demux_data->i_PSM_version = EMPTY_PSM_VERSION; + p_dvd = (thread_dvd_data_t*)p_input->p_plugin_data; + p_netlist = (dvd_netlist_t *)p_input->p_method_data; - while( !p_input->b_die && !p_input->b_error - && !p_demux_data->b_has_PSM ) + if( p_dvd->b_encrypted ) + { + free( p_dvd->p_css ); + } + + IfoDestroy( p_dvd->p_ifo ); + free( p_dvd ); + DVDNetlistEnd( p_netlist ); +} + +/***************************************************************************** + * DVDSetArea: initialize input data for title x, chapter y. + * It should be called for each user navigation request. + ***************************************************************************** + * Take care that i_title starts from 0 (vmg) and i_chapter start from 1. + * Note that you have to take the lock before entering here. + *****************************************************************************/ +static int DVDSetArea( input_thread_t * p_input, input_area_t * p_area ) +{ + thread_dvd_data_t * p_dvd; + es_descriptor_t * p_es; + u16 i_id; + int i_vts_title; + int i_audio; + int i_spu; + int i; + int j; + + p_dvd = (thread_dvd_data_t*)p_input->p_plugin_data; + + /* we can't use the interface slider until initilization is complete */ + p_input->stream.b_seekable = 0; + + if( p_area != p_input->stream.p_selected_area ) + { + + /* + * We have to load all title information + */ + /* Change the default area */ + p_input->stream.p_selected_area = + p_input->stream.pp_areas[p_area->i_id]; + + /* release the lock to to let the interface go */ +// vlc_mutex_unlock( &p_input->stream.stream_lock ); + + /* title number: it is not vts nb!, + * it is what appears in the interface list */ + p_dvd->i_title = p_area->i_id; + p_dvd->p_ifo->i_title = p_dvd->i_title; + + /* set number of chapters of current title */ + p_dvd->i_chapter_nb = p_area->i_part_nb; + + /* ifo vts */ + if( IfoTitleSet( p_dvd->p_ifo ) < 0 ) { - int i_result, i; - data_packet_t * pp_packets[INPUT_READ_ONCE]; + intf_ErrMsg( "dvd error: fatal error in vts ifo" ); + free( p_dvd ); + p_input->b_error = 1; + return -1; + } + +#define vmg p_dvd->p_ifo->vmg +#define vts p_dvd->p_ifo->vts + /* title position inside the selected vts */ + i_vts_title = vmg.title_inf.p_attr[p_dvd->i_title-1].i_title_num; + p_dvd->i_title_id = + vts.title_inf.p_title_start[i_vts_title-1].i_title_id; + + intf_WarnMsg( 3, "dvd: title %d vts_title %d pgc %d", + p_dvd->i_title, + i_vts_title, + p_dvd->i_title_id ); - i_result = DVDRead( p_input, pp_packets ); - if( i_result == 1 ) + /* css title key for current vts */ + if( p_dvd->b_encrypted ) + { + /* this one is vts number */ + p_dvd->p_css->i_title = + vmg.title_inf.p_attr[p_dvd->i_title-1].i_title_set_num; + p_dvd->p_css->i_title_pos = + vts.i_pos + + vts.manager_inf.i_title_vob_start_sector * DVD_LB_SIZE; + + j = CSSGetKey( p_input->i_handle, p_dvd->p_css ); + if( j < 0 ) { - /* EOF */ - vlc_mutex_lock( &p_input->stream.stream_lock ); - p_input->stream.pp_programs[0]->b_is_ok = 1; - vlc_mutex_unlock( &p_input->stream.stream_lock ); - break; + intf_ErrMsg( "dvd error: fatal error in vts css key" ); + free( p_dvd ); + p_input->b_error = 1; + return -1; } - if( i_result == -1 ) + else if( j > 0 ) { + intf_ErrMsg( "dvd error: css decryption unavailable" ); + free( p_dvd ); p_input->b_error = 1; - break; + return -1; } + } - for( i = 0; i < INPUT_READ_ONCE && pp_packets[i] != NULL; i++ ) - { - /* FIXME: use i_p_config_t */ - input_ParsePS( p_input, pp_packets[i] ); - input_NetlistDeletePacket( p_input->p_method_data, - pp_packets[i] ); - } + /* + * Angle management + */ + p_dvd->i_angle_nb = vmg.title_inf.p_attr[p_dvd->i_title-1].i_angle_nb; + p_dvd->i_angle = main_GetIntVariable( INPUT_ANGLE_VAR, 1 ); + if( ( p_dvd->i_angle <= 0 ) || p_dvd->i_angle > p_dvd->i_angle_nb ) + { + p_dvd->i_angle = 1; + } + + /* + * Set selected title start and size + */ + + /* title set offset */ + p_dvd->i_title_start = vts.i_pos + DVD_LB_SIZE * + (off_t)( vts.manager_inf.i_title_vob_start_sector ); + + /* last video cell */ + p_dvd->i_cell = 0; + p_dvd->i_prg_cell = -1 + + vts.title_unit.p_title[p_dvd->i_title_id-1].title.i_cell_nb; + + if( DVDFindCell( p_dvd ) < 0 ) + { + intf_ErrMsg( "dvd error: can't find title end" ); + p_input->b_error = 1; + return -1; + } - /* File too big. */ - if( p_input->stream.i_tell > INPUT_PREPARSE_LENGTH ) + /* temporary hack to fix size in some dvds */ + if( p_dvd->i_cell >= vts.cell_inf.i_cell_nb ) + { + p_dvd->i_cell = vts.cell_inf.i_cell_nb - 1; + } + + p_dvd->i_sector = 0; + p_dvd->i_size = DVD_LB_SIZE * + (off_t)( vts.cell_inf.p_cell_map[p_dvd->i_cell].i_end_sector ); + intf_WarnMsg( 2, "dvd info: stream size 1: %lld @ %d", p_dvd->i_size, + vts.cell_inf.p_cell_map[p_dvd->i_cell].i_end_sector ); + + if( DVDChapterSelect( p_dvd, 1 ) < 0 ) + { + intf_ErrMsg( "dvd error: can't find first chapter" ); + p_input->b_error = 1; + return -1; + } + + p_dvd->i_size -= (off_t)( p_dvd->i_sector + 1 ) *DVD_LB_SIZE; + + IfoPrintTitle( p_dvd ); + +// vlc_mutex_lock( &p_input->stream.stream_lock ); + + /* Area definition */ + p_input->stream.p_selected_area->i_start = p_dvd->i_start; + p_input->stream.p_selected_area->i_size = p_dvd->i_size; + p_input->stream.p_selected_area->i_angle_nb = p_dvd->i_angle_nb; + p_input->stream.p_selected_area->i_angle = p_dvd->i_angle; + + /* + * Destroy obsolete ES by reinitializing program 0 + * and find all ES in title with ifo data + */ + if( p_input->stream.pp_programs != NULL ) + { + /* We don't use input_EndStream here since + * we keep area structures */ + + for( i = 0 ; i < p_input->stream.i_selected_es_number ; i++ ) { - break; + input_UnselectES( p_input, p_input->stream.pp_selected_es[i] ); } + + input_DelProgram( p_input, p_input->stream.pp_programs[0] ); + + p_input->stream.pp_selected_es = NULL; + p_input->stream.i_selected_es_number = 0; } - lseek( p_input->i_handle, i_start, SEEK_SET ); - vlc_mutex_lock( &p_input->stream.stream_lock ); - p_input->stream.i_tell = 0; - if( p_demux_data->b_has_PSM ) + + input_AddProgram( p_input, 0, sizeof( stream_ps_data_t ) ); + + /* No PSM to read in DVD mode, we already have all information */ + p_input->stream.pp_programs[0]->b_is_ok = 1; + p_input->stream.pp_programs[0]->i_synchro_state = SYNCHRO_START; + + p_es = NULL; + + /* ES 0 -> video MPEG2 */ + IfoPrintVideo( p_dvd ); + + p_es = input_AddES( p_input, p_input->stream.pp_programs[0], 0xe0, 0 ); + p_es->i_stream_id = 0xe0; + p_es->i_type = MPEG2_VIDEO_ES; + p_es->i_cat = VIDEO_ES; + intf_WarnMsg( 1, "dvd info: video mpeg2 stream" ); + if( p_main->b_video ) { - /* (The PSM decoder will care about spawning the decoders) */ - p_input->stream.pp_programs[0]->b_is_ok = 1; + input_SelectES( p_input, p_es ); } -#ifdef AUTO_SPAWN - else + intf_WarnMsg( 4, "dvd info: video selected" ); + +#define audio_status \ + vts.title_unit.p_title[p_dvd->i_title_id-1].title.pi_audio_status[i-1] + /* Audio ES, in the order they appear in .ifo */ + for( i = 1 ; i <= vts.manager_inf.i_audio_nb ; i++ ) { - /* (We have to do it ourselves) */ - int i_es; - - /* FIXME: we should do multiple passes in case an audio type - * is not present */ - for( i_es = 0; - i_es < p_input->stream.pp_programs[0]->i_es_number; - i_es++ ) + IfoPrintAudio( p_dvd, i ); + + /* audio channel is active if first byte is 0x80 */ + if( audio_status.i_available ) { -#define p_es p_input->stream.pp_programs[0]->pp_es[i_es] - switch( p_es->i_type ) + switch( vts.manager_inf.p_audio_attr[i-1].i_coding_mode ) { - case MPEG1_VIDEO_ES: - case MPEG2_VIDEO_ES: - input_SelectES( p_input, p_es ); - break; + case 0x00: /* AC3 */ + i_id = ( ( 0x80 + audio_status.i_position ) << 8 ) | 0xbd; + p_es = input_AddES( p_input, + p_input->stream.pp_programs[0], i_id, 0 ); + p_es->i_stream_id = 0xbd; + p_es->i_type = AC3_AUDIO_ES; + p_es->b_audio = 1; + p_es->i_cat = AUDIO_ES; + strcpy( p_es->psz_desc, IfoLanguage( hton16( + vts.manager_inf.p_audio_attr[i-1].i_lang_code ) ) ); + strcat( p_es->psz_desc, " (ac3)" ); + + intf_WarnMsg( 3, "dvd info: audio stream %d %s\t(0x%x)", + i, p_es->psz_desc, i_id ); + + break; + case 0x02: + case 0x03: /* MPEG audio */ + i_id = 0xc0 + audio_status.i_position; + p_es = input_AddES( p_input, + p_input->stream.pp_programs[0], i_id, 0 ); + p_es->i_stream_id = i_id; + p_es->i_type = MPEG2_AUDIO_ES; + p_es->b_audio = 1; + p_es->i_cat = AUDIO_ES; + strcpy( p_es->psz_desc, IfoLanguage( hton16( + vts.manager_inf.p_audio_attr[i-1].i_lang_code ) ) ); + strcat( p_es->psz_desc, " (mpeg)" ); + + intf_WarnMsg( 3, "dvd info: audio stream %d %s\t(0x%x)", + i, p_es->psz_desc, i_id ); + + break; + case 0x04: /* LPCM */ + + i_id = ( ( 0xa0 + audio_status.i_position ) << 8 ) | 0xbd; + p_es = input_AddES( p_input, + p_input->stream.pp_programs[0], i_id, 0 ); + p_es->i_stream_id = i_id; + p_es->i_type = LPCM_AUDIO_ES; + p_es->b_audio = 1; + p_es->i_cat = AUDIO_ES; + strcpy( p_es->psz_desc, IfoLanguage( hton16( + vts.manager_inf.p_audio_attr[i-1].i_lang_code ) ) ); + strcat( p_es->psz_desc, " (lpcm)" ); + + intf_WarnMsg( 3, "dvd info: audio stream %d %s\t(0x%x)", + i, p_es->psz_desc, i_id ); + break; + case 0x06: /* DTS */ + i_id = ( ( 0x88 + audio_status.i_position ) << 8 ) | 0xbd; + intf_ErrMsg( "dvd warning: DTS audio not handled yet" + "(0x%x)", i_id ); + break; + default: + i_id = 0; + intf_ErrMsg( "dvd warning: unknown audio type %.2x", + vts.manager_inf.p_audio_attr[i-1].i_coding_mode ); + } + } + } +#undef audio_status +#define spu_status \ + vts.title_unit.p_title[p_dvd->i_title_id-1].title.pi_spu_status[i-1] - case MPEG1_AUDIO_ES: - case MPEG2_AUDIO_ES: - if( main_GetIntVariable( INPUT_CHANNEL_VAR, 0 ) - == (p_es->i_id & 0x1F) ) - switch( main_GetIntVariable( INPUT_AUDIO_VAR, 0 ) ) - { - case 0: - main_PutIntVariable( INPUT_AUDIO_VAR, - REQUESTED_MPEG ); - case REQUESTED_MPEG: - input_SelectES( p_input, p_es ); - } - break; + /* Sub Picture ES */ + + for( i = 1 ; i <= vts.manager_inf.i_spu_nb; i++ ) + { + IfoPrintSpu( p_dvd, i ); - case AC3_AUDIO_ES: - if( main_GetIntVariable( INPUT_CHANNEL_VAR, 0 ) - == ((p_es->i_id & 0xF00) >> 8) ) - switch( main_GetIntVariable( INPUT_AUDIO_VAR, 0 ) ) - { - case 0: - main_PutIntVariable( INPUT_AUDIO_VAR, - REQUESTED_AC3 ); - case REQUESTED_AC3: - input_SelectES( p_input, p_es ); - } + if( spu_status.i_available ) + { + /* there are several streams for one spu */ + if( vts.manager_inf.video_attr.i_ratio ) + { + /* 16:9 */ + switch( vts.manager_inf.video_attr.i_perm_displ ) + { + case 1: + i_id = ( ( 0x20 + spu_status.i_position_pan ) << 8 ) + | 0xbd; break; - - case DVD_SPU_ES: - if( main_GetIntVariable( INPUT_SUBTITLE_VAR, -1 ) - == ((p_es->i_id & 0x1F00) >> 8) ) - { - input_SelectES( p_input, p_es ); - } + case 2: + i_id = ( ( 0x20 + spu_status.i_position_letter ) << 8 ) + | 0xbd; break; - - case LPCM_AUDIO_ES: - /* FIXME ! */ + default: + i_id = ( ( 0x20 + spu_status.i_position_wide ) << 8 ) + | 0xbd; break; + } } + else + { + /* 4:3 */ + i_id = ( ( 0x20 + spu_status.i_position_43 ) << 8 ) + | 0xbd; + } + p_es = input_AddES( p_input, + p_input->stream.pp_programs[0], i_id, 0 ); + p_es->i_stream_id = 0xbd; + p_es->i_type = DVD_SPU_ES; + p_es->i_cat = SPU_ES; + strcpy( p_es->psz_desc, IfoLanguage( hton16( + vts.manager_inf.p_spu_attr[i-1].i_lang_code ) ) ); + intf_WarnMsg( 3, "dvd info: spu stream %d %s\t(0x%x)", + i, p_es->psz_desc, i_id ); } - } -#endif -#ifdef STATS - input_DumpStream( p_input ); -#endif - vlc_mutex_unlock( &p_input->stream.stream_lock ); - } +#undef spu_status + if( p_main->b_audio ) + { + /* For audio: first one if none or a not existing one specified */ + i_audio = main_GetIntVariable( INPUT_CHANNEL_VAR, 1 ); + if( i_audio < 0 || i_audio > vts.manager_inf.i_audio_nb ) + { + main_PutIntVariable( INPUT_CHANNEL_VAR, 1 ); + i_audio = 1; + } + if( i_audio > 0 && vts.manager_inf.i_audio_nb > 0 ) + { + input_SelectES( p_input, p_input->stream.pp_es[i_audio] ); + } + } + + if( p_main->b_video ) + { + /* for spu, default is none */ + i_spu = main_GetIntVariable( INPUT_SUBTITLE_VAR, 0 ); + if( i_spu < 0 || i_spu > vts.manager_inf.i_spu_nb ) + { + main_PutIntVariable( INPUT_SUBTITLE_VAR, 0 ); + i_spu = 0; + } + if( i_spu > 0 && vts.manager_inf.i_spu_nb > 0 ) + { + i_spu += vts.manager_inf.i_audio_nb; + input_SelectES( p_input, p_input->stream.pp_es[i_spu] ); + } + } + } /* i_title >= 0 */ else { - /* The programs will be added when we read them. */ - vlc_mutex_lock( &p_input->stream.stream_lock ); - p_input->stream.pp_programs[0]->b_is_ok = 0; - vlc_mutex_unlock( &p_input->stream.stream_lock ); + p_area = p_input->stream.p_selected_area; } +#undef vts +#undef vmg -} + /* + * Chapter selection + */ -/***************************************************************************** - * DVDOpen : open the dvd device - *****************************************************************************/ -static void DVDOpen( input_thread_t * p_input ) -{ - intf_Msg( "input: opening DVD %s", p_input->p_source ); - - p_input->i_handle = open( p_input->p_source, O_RDONLY | O_NONBLOCK ); - - if( p_input->i_handle == -1 ) + + if( p_area->i_part != p_dvd->i_chapter ) { - intf_ErrMsg( "input error: cannot open device (%s)", strerror(errno) ); - p_input->b_error = 1; - return; + if( ( p_area->i_part > 0 ) && + ( p_area->i_part <= p_area->i_part_nb )) + { + if( DVDChapterSelect( p_dvd, p_area->i_part ) < 0 ) + { + intf_ErrMsg( "dvd error: can't set chapter in area" ); + p_input->b_error = 1; + return -1; + } + + p_input->stream.p_selected_area->i_tell = p_dvd->i_start - + p_area->i_start; + p_input->stream.p_selected_area->i_part = p_dvd->i_chapter; + + intf_WarnMsg( 2, "dvd info: chapter %d start at: %lld", + p_area->i_part, p_area->i_tell ); + } + else + { + p_area->i_part = 1; + p_dvd->i_chapter = 1; + } } - vlc_mutex_lock( &p_input->stream.stream_lock ); +#define title \ + p_dvd->p_ifo->vts.title_unit.p_title[p_dvd->i_title_id-1].title + if( p_area->i_angle != p_dvd->i_angle ) + { + if( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 ) + { + if( ( p_area->i_angle - p_dvd->i_angle ) < 0 ) + { + p_dvd->i_cell = 0; + } + p_dvd->i_prg_cell += ( p_area->i_angle - p_dvd->i_angle ); + p_dvd->i_angle = p_area->i_angle; + + DVDFindSector( p_dvd ); + p_dvd->i_cell += p_dvd->i_angle_cell; + } + else + { + p_dvd->i_angle = p_area->i_angle; + } - p_input->stream.b_pace_control = 1; - p_input->stream.b_seekable = 1; - p_input->stream.i_size = 0; - p_input->stream.i_tell = 0; + intf_WarnMsg( 2, "dvd info: angle %d selected", p_area->i_angle ); + } - vlc_mutex_unlock( &p_input->stream.stream_lock ); -} + /* warn interface that something has changed */ + p_input->stream.b_seekable = 1; + p_input->stream.b_changed = 1; -/***************************************************************************** - * DVDClose : close a file descriptor - *****************************************************************************/ -static void DVDClose( input_thread_t * p_input ) -{ - close( p_input->i_handle ); + p_input->stream.pp_programs[0]->i_synchro_state = SYNCHRO_REINIT; - return; + return 0; } -/***************************************************************************** - * DVDEnd: frees unused data - *****************************************************************************/ -static void DVDEnd( input_thread_t * p_input ) -{ - /* FIXME: check order of calls */ -// CSSEnd( p_input ); -// IfoEnd( (ifo_t*)(&p_input->p_plugin_data->ifo ) ); - free( p_input->stream.p_demux_data ); - free( p_input->p_plugin_data ); - input_NetlistEnd( p_input ); -} /***************************************************************************** * DVDRead: reads data packets into the netlist. @@ -401,62 +784,134 @@ static void DVDEnd( input_thread_t * p_input ) * EOF. *****************************************************************************/ static int DVDRead( input_thread_t * p_input, - data_packet_t ** pp_packets ) + data_packet_t ** pp_packets ) { - thread_dvd_data_t * p_method; - netlist_t * p_netlist; + thread_dvd_data_t * p_dvd; + dvd_netlist_t * p_netlist; struct iovec * p_vec; - struct data_packet_s * p_data; + struct data_packet_s * pp_data[p_input->i_read_once]; u8 * pi_cur; + int i_block_once; int i_packet_size; + int i_iovec; int i_packet; int i_pos; - int i; - boolean_t b_first_packet; + int i_read_bytes; + int i_read_blocks; + off_t i_off; + boolean_t b_eof; + boolean_t b_eot; - p_method = ( thread_dvd_data_t * ) p_input->p_plugin_data; - p_netlist = ( netlist_t * ) p_input->p_method_data; + p_dvd = (thread_dvd_data_t *)p_input->p_plugin_data; + p_netlist = (dvd_netlist_t *)p_input->p_method_data; /* Get an iovec pointer */ - if( ( p_vec = input_NetlistGetiovec( p_netlist, &p_data ) ) == NULL ) + if( ( p_vec = DVDGetiovec( p_netlist ) ) == NULL ) { - intf_ErrMsg( "DVD: read error" ); + intf_ErrMsg( "dvd error: can't get iovec" ); return -1; } - /* Reads from DVD */ - readv( p_input->i_handle, p_vec, p_method->i_read_once ); + i_block_once = p_dvd->i_end_sector - p_dvd->i_sector + 1; -#if defined( HAVE_SYS_DVDIO_H ) || defined( LINUX_DVD ) - if( p_method->b_encrypted ) + /* Get the position of the next cell if we're at cell end */ + if( i_block_once <= 0 ) { - for( i=0 ; ii_read_once ; i++ ) + int i_angle; + + p_dvd->i_cell++; + p_dvd->i_angle_cell++; + + /* Find cell index in adress map */ + if( DVDFindSector( p_dvd ) < 0 ) { - CSSDescrambleSector( - p_method->css.p_title_key[p_method->i_title].key, - p_vec[i].iov_base ); - ((u8*)(p_vec[i].iov_base))[0x14] &= 0x8F; + pp_packets[0] = NULL; + intf_ErrMsg( "dvd error: can't find next cell" ); + return 1; } + + /* Position the fd pointer on the right address */ + i_off = lseek( p_dvd->i_fd, + p_dvd->i_title_start + + (off_t)( p_dvd->i_sector ) *DVD_LB_SIZE, SEEK_SET ); + + /* update chapter : it will be easier when we have navigation + * ES support */ + if( p_dvd->i_chapter < ( p_dvd->i_chapter_nb - 1 ) ) + { + if( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 ) + { + i_angle = p_dvd->i_angle - 1; + } + else + { + i_angle = 0; + } + if( title.chapter_map.pi_start_cell[p_dvd->i_chapter] <= + ( p_dvd->i_prg_cell - i_angle + 1 ) ) + { + p_dvd->i_chapter++; + } + } + + vlc_mutex_lock( &p_input->stream.stream_lock ); + + p_input->stream.p_selected_area->i_tell = i_off - + p_input->stream.p_selected_area->i_start; + p_input->stream.p_selected_area->i_part = p_dvd->i_chapter; + + /* the synchro has to be reinitialized when we change cell */ + p_input->stream.pp_programs[0]->i_synchro_state = SYNCHRO_REINIT; + + vlc_mutex_unlock( &p_input->stream.stream_lock ); + + i_block_once = p_dvd->i_end_sector - p_dvd->i_sector + 1; } -#endif + + /* the number of blocks read is the maw between the requested + * value and the leaving block in the cell */ + if( i_block_once > p_dvd->i_block_once ) + { + i_block_once = p_dvd->i_block_once; + } +//intf_WarnMsg( 2, "Sector: 0x%x Read: %d Chapter: %d", p_dvd->i_sector, i_block_once, p_dvd->i_chapter ); + + p_netlist->i_read_once = i_block_once; + + /* Reads from DVD */ + i_read_bytes = readv( p_dvd->i_fd, p_vec, i_block_once ); + i_read_blocks = ( i_read_bytes + 0x7ff ) >> 11; /* Update netlist indexes */ - input_NetlistMviovec( p_netlist, p_method->i_read_once ); + DVDMviovec( p_netlist, i_read_blocks, pp_data ); + + /* Update global position */ + p_dvd->i_sector += i_read_blocks; i_packet = 0; + /* Read headers to compute payload length */ - for( i = 0 ; i < p_method->i_read_once ; i++ ) + for( i_iovec = 0 ; i_iovec < i_read_blocks ; i_iovec++ ) { + if( p_dvd->b_encrypted ) + { + CSSDescrambleSector( p_dvd->p_css->pi_title_key, + p_vec[i_iovec].iov_base ); + ((u8*)(p_vec[i_iovec].iov_base))[0x14] &= 0x8F; + } + i_pos = 0; - b_first_packet = 1; + while( i_pos < p_netlist->i_buffer_size ) { - pi_cur = (u8*)(p_vec[i].iov_base + i_pos); + pi_cur = (u8*)(p_vec[i_iovec].iov_base + i_pos); + /*default header */ if( U32_AT( pi_cur ) != 0x1BA ) { /* That's the case for all packets, except pack header. */ i_packet_size = U16_AT( pi_cur + 4 ); + pp_packets[i_packet] = DVDNewPtr( p_netlist ); } else { @@ -476,35 +931,63 @@ static int DVDRead( input_thread_t * p_input, intf_ErrMsg( "Unable to determine stream type" ); return( -1 ); } + + pp_packets[i_packet] = pp_data[i_iovec]; + } - if( b_first_packet ) - { - p_data->b_discard_payload = 0; - b_first_packet = 0; - } - else - { - p_data = input_NetlistNewPacket( p_netlist , - i_packet_size + 6 ); - memcpy( p_data->p_buffer, - p_vec[i].iov_base + i_pos , i_packet_size + 6 ); - } - p_data->p_payload_end = p_data->p_payload_start + i_packet_size + 6; - pp_packets[i_packet] = p_data; + (*pp_data[i_iovec]->pi_refcount)++; + + pp_packets[i_packet]->pi_refcount = pp_data[i_iovec]->pi_refcount; + + pp_packets[i_packet]->p_buffer = pp_data[i_iovec]->p_buffer; + + pp_packets[i_packet]->p_payload_start = + pp_packets[i_packet]->p_buffer + i_pos; + + pp_packets[i_packet]->p_payload_end = + pp_packets[i_packet]->p_payload_start + i_packet_size + 6; + + pp_packets[i_packet]->p_next = NULL; + pp_packets[i_packet]->b_discard_payload = 0; + i_packet++; i_pos += i_packet_size + 6; } } + pp_packets[i_packet] = NULL; vlc_mutex_lock( &p_input->stream.stream_lock ); - p_input->stream.i_tell += p_method->i_read_once *DVD_LB_SIZE; + + p_input->stream.p_selected_area->i_tell += i_read_bytes; + b_eot = !( p_input->stream.p_selected_area->i_tell < p_dvd->i_size ); + b_eof = b_eot && ( ( p_dvd->i_title + 1 ) >= p_input->stream.i_area_nb ); + vlc_mutex_unlock( &p_input->stream.stream_lock ); - return( 0 ); -} + if( b_eof ) + { + return 1; + } + + if( b_eot ) + { + intf_WarnMsg( 4, "dvd info: new title" ); + p_dvd->i_title++; + vlc_mutex_lock( &p_input->stream.stream_lock ); + DVDSetArea( p_input, p_input->stream.pp_areas[p_dvd->i_title] ); + vlc_mutex_unlock( &p_input->stream.stream_lock ); + return 0; + } + + if( i_read_blocks == i_block_once ) + { + return 0; + } + return -1; +} /***************************************************************************** * DVDRewind : reads a stream backward @@ -515,11 +998,264 @@ static int DVDRewind( input_thread_t * p_input ) } /***************************************************************************** - * DVDSeek : Goes to a given position on the stream ; this one is used by the - * input and translate chronological position from input to logical postion - * on the device + * DVDSeek : Goes to a given position on the stream. + ***************************************************************************** + * This one is used by the input and translate chronological position from + * input to logical position on the device. + * The lock should be taken before calling this function. *****************************************************************************/ static void DVDSeek( input_thread_t * p_input, off_t i_off ) { + thread_dvd_data_t * p_dvd; + off_t i_pos; + int i_prg_cell; + int i_cell; + int i_chapter; + int i_angle; + + p_dvd = ( thread_dvd_data_t * )p_input->p_plugin_data; + + /* we have to take care of offset of beginning of title */ + i_pos = i_off + p_input->stream.p_selected_area->i_start + - p_dvd->i_title_start; + + /* update navigation data */ + p_dvd->i_sector = i_pos >> 11; + + i_prg_cell = 0; + i_chapter = 0; + + /* parse vobu address map to find program cell */ + while( title.p_cell_play[i_prg_cell].i_end_sector < p_dvd->i_sector ) + { + i_prg_cell++; + } + + p_dvd->i_prg_cell = i_prg_cell; + + if( DVDChooseAngle( p_dvd ) < 0 ) + { + p_input->b_error = 1; + return; + } + + p_dvd->i_cell = 0; + + /* Find first title cell which is inside program cell */ + if( DVDFindCell( p_dvd ) < 0 ) + { + /* no following cell : we're at eof */ + intf_ErrMsg( "dvd error: cell seeking failed" ); + p_input->b_error = 1; + return; + } + + i_cell = p_dvd->i_cell; + +#define cell p_dvd->p_ifo->vts.cell_inf.p_cell_map[i_cell] + /* parse cell address map to find title cell containing sector */ + while( cell.i_end_sector < p_dvd->i_sector ) + { + i_cell++; + } + + p_dvd->i_cell = i_cell; + + /* if we're inside a multi-angle zone, we have to choose i_sector + * in the current angle ; we can't do it all the time since cells + * can be very wide out of such zones */ + if( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 ) + { + p_dvd->i_sector = MAX( + cell.i_start_sector, + title.p_cell_play[p_dvd->i_prg_cell].i_start_sector ); + } + + p_dvd->i_end_sector = MIN( + cell.i_end_sector, + title.p_cell_play[p_dvd->i_prg_cell].i_end_sector ); +#undef cell + /* update chapter */ + if( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 ) + { + i_angle = p_dvd->i_angle - 1; + } + else + { + i_angle = 0; + } + while( ( title.chapter_map.pi_start_cell[i_chapter] <= + ( p_dvd->i_prg_cell - i_angle + 1 ) ) && + ( i_chapter < ( p_dvd->i_chapter_nb - 1 ) ) ) + { + i_chapter++; + } + + p_dvd->i_chapter = i_chapter; + p_input->stream.p_selected_area->i_part = p_dvd->i_chapter; + + p_input->stream.p_selected_area->i_tell = + lseek( p_dvd->i_fd, p_dvd->i_title_start + + (off_t)( p_dvd->i_sector ) *DVD_LB_SIZE, SEEK_SET ) - + p_input->stream.p_selected_area->i_start; +/* + intf_WarnMsg( 3, "Program Cell: %d Cell: %d Chapter: %d", + p_dvd->i_prg_cell, p_dvd->i_cell, p_dvd->i_chapter ); +*/ + return; } + +#define cell p_dvd->p_ifo->vts.cell_inf + +/***************************************************************************** + * DVDFindCell: adjust the title cell index with the program cell + *****************************************************************************/ +static int DVDFindCell( thread_dvd_data_t * p_dvd ) +{ + int i_cell; + int i_index; + + i_cell = p_dvd->i_cell; + i_index = p_dvd->i_prg_cell; + + if( i_cell >= cell.i_cell_nb ) + { + return -1; + } + + while( ( ( title.p_cell_pos[i_index].i_vob_id != + cell.p_cell_map[i_cell].i_vob_id ) || + ( title.p_cell_pos[i_index].i_cell_id != + cell.p_cell_map[i_cell].i_cell_id ) ) && + ( i_cell < cell.i_cell_nb - 1 ) ) + { + i_cell++; + } + +/* +intf_WarnMsg( 3, "FindCell: i_cell %d i_index %d found %d nb %d", + p_dvd->i_cell, + p_dvd->i_prg_cell, + i_cell, + cell.i_cell_nb ); +*/ + + p_dvd->i_cell = i_cell; + + return 0; +} + +#undef cell + +/***************************************************************************** + * DVDFindSector: find cell index in adress map from index in + * information table program map and give corresponding sectors. + *****************************************************************************/ +static int DVDFindSector( thread_dvd_data_t * p_dvd ) +{ + + if( p_dvd->i_sector > title.p_cell_play[p_dvd->i_prg_cell].i_end_sector ) + { + p_dvd->i_prg_cell++; + + if( DVDChooseAngle( p_dvd ) < 0 ) + { + return -1; + } + } + + if( DVDFindCell( p_dvd ) < 0 ) + { + intf_ErrMsg( "dvd error: can't find sector" ); + return -1; + } + + /* Find start and end sectors of new cell */ +#if 1 + p_dvd->i_sector = MAX( + p_dvd->p_ifo->vts.cell_inf.p_cell_map[p_dvd->i_cell].i_start_sector, + title.p_cell_play[p_dvd->i_prg_cell].i_start_sector ); + p_dvd->i_end_sector = MIN( + p_dvd->p_ifo->vts.cell_inf.p_cell_map[p_dvd->i_cell].i_end_sector, + title.p_cell_play[p_dvd->i_prg_cell].i_end_sector ); +#else + p_dvd->i_sector = title.p_cell_play[p_dvd->i_prg_cell].i_start_sector; + p_dvd->i_end_sector = title.p_cell_play[p_dvd->i_prg_cell].i_end_sector; +#endif + +/* + intf_WarnMsg( 3, "cell: %d sector1: 0x%x end1: 0x%x\n" + "index: %d sector2: 0x%x end2: 0x%x\n" + "category: 0x%x ilvu end: 0x%x vobu start 0x%x", + p_dvd->i_cell, + p_dvd->p_ifo->vts.cell_inf.p_cell_map[p_dvd->i_cell].i_start_sector, + p_dvd->p_ifo->vts.cell_inf.p_cell_map[p_dvd->i_cell].i_end_sector, + p_dvd->i_prg_cell, + title.p_cell_play[p_dvd->i_prg_cell].i_start_sector, + title.p_cell_play[p_dvd->i_prg_cell].i_end_sector, + title.p_cell_play[p_dvd->i_prg_cell].i_category, + title.p_cell_play[p_dvd->i_prg_cell].i_first_ilvu_vobu_esector, + title.p_cell_play[p_dvd->i_prg_cell].i_last_vobu_start_sector ); +*/ + + return 0; +} + +/***************************************************************************** + * DVDChapterSelect: find the cell corresponding to requested chapter + *****************************************************************************/ +static int DVDChapterSelect( thread_dvd_data_t * p_dvd, int i_chapter ) +{ + + /* Find cell index in Program chain for current chapter */ + p_dvd->i_prg_cell = title.chapter_map.pi_start_cell[i_chapter-1] - 1; + p_dvd->i_cell = 0; + p_dvd->i_sector = 0; + + DVDChooseAngle( p_dvd ); + + /* Search for cell_index in cell adress_table and initialize + * start sector */ + if( DVDFindSector( p_dvd ) < 0 ) + { + intf_ErrMsg( "dvd error: can't select chapter" ); + return -1; + } + + /* start is : beginning of vts vobs + offset to vob x */ + p_dvd->i_start = p_dvd->i_title_start + + DVD_LB_SIZE * (off_t)( p_dvd->i_sector ); + + /* Position the fd pointer on the right address */ + p_dvd->i_start = lseek( p_dvd->i_fd, p_dvd->i_start, SEEK_SET ); + + p_dvd->i_chapter = i_chapter; + return 0; +} + +/***************************************************************************** + * DVDChooseAngle: select the cell corresponding to the selected angle + *****************************************************************************/ +static int DVDChooseAngle( thread_dvd_data_t * p_dvd ) +{ + /* basic handling of angles */ + switch( ( ( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 ) + >> 12 ) ) + { + /* we enter a muli-angle section */ + case 0x5: + p_dvd->i_prg_cell += p_dvd->i_angle - 1; + p_dvd->i_angle_cell = 0; + break; + /* we exit a multi-angle section */ + case 0x9: + case 0xd: + p_dvd->i_prg_cell += p_dvd->i_angle_nb - p_dvd->i_angle; + break; + } + + return 0; +} + +#undef title