You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
494 lines
16 KiB
494 lines
16 KiB
/*****************************************************************************
|
|
* vcd.c : VCD input module for vlc
|
|
*****************************************************************************
|
|
* Copyright © 2000-2011 VLC authors and VideoLAN
|
|
*
|
|
* Author: Johan Bilien <jobi@via.ecp.fr>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Preamble
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_input.h>
|
|
#include <vlc_access.h>
|
|
#include <vlc_charset.h>
|
|
|
|
#include "cdrom.h"
|
|
|
|
/*****************************************************************************
|
|
* Module descriptor
|
|
*****************************************************************************/
|
|
static int Open ( vlc_object_t * );
|
|
static void Close( vlc_object_t * );
|
|
|
|
#define HELP_TEXT N_("Usage hint: [vcd:][device][#[title][,[chapter]]]")
|
|
|
|
vlc_module_begin ()
|
|
set_shortname( N_("VCD"))
|
|
set_description( N_("VCD input") )
|
|
set_help( HELP_TEXT )
|
|
set_capability( "access", 0 )
|
|
set_callbacks( Open, Close )
|
|
set_subcategory( SUBCAT_INPUT_ACCESS )
|
|
|
|
add_shortcut( "vcd", "svcd" )
|
|
vlc_module_end ()
|
|
|
|
/*****************************************************************************
|
|
* Local prototypes
|
|
*****************************************************************************/
|
|
|
|
/* how many blocks VCDRead will read in each loop */
|
|
#define VCD_BLOCKS_ONCE 20
|
|
#define VCD_DATA_ONCE (VCD_BLOCKS_ONCE * VCD_DATA_SIZE)
|
|
|
|
typedef struct
|
|
{
|
|
vcddev_t *vcddev; /* vcd device descriptor */
|
|
uint64_t offset;
|
|
|
|
/* Title infos */
|
|
vcddev_toc_t *p_toc;
|
|
|
|
struct
|
|
{
|
|
uint64_t *seekpoints;
|
|
size_t count;
|
|
} titles[99]; /* No more that 99 track in a vcd ? */
|
|
int i_current_title;
|
|
unsigned i_current_seekpoint;
|
|
int i_sector; /* Current Sector */
|
|
} access_sys_t;
|
|
|
|
static block_t *Block( stream_t *, bool * restrict);
|
|
static int Seek( stream_t *, uint64_t );
|
|
static int Control( stream_t *, int, va_list );
|
|
static int EntryPoints( stream_t * );
|
|
|
|
/*****************************************************************************
|
|
* VCDOpen: open vcd
|
|
*****************************************************************************/
|
|
static int Open( vlc_object_t *p_this )
|
|
{
|
|
stream_t *p_access = (stream_t *)p_this;
|
|
access_sys_t *p_sys;
|
|
if( p_access->psz_filepath == NULL )
|
|
return VLC_EGENERIC;
|
|
|
|
char *psz_dup = ToLocaleDup( p_access->psz_filepath );
|
|
char *psz;
|
|
int i_title = 0;
|
|
int i_chapter = 0;
|
|
vcddev_t *vcddev;
|
|
|
|
/* Command line: vcd://[dev_path][#title[,chapter]] */
|
|
if( ( psz = strchr( psz_dup, '#' ) ) )
|
|
{
|
|
*psz++ = '\0';
|
|
|
|
i_title = strtol( psz, &psz, 0 );
|
|
if( *psz )
|
|
i_chapter = strtol( psz+1, &psz, 0 );
|
|
}
|
|
|
|
if( *psz_dup == '\0' )
|
|
{
|
|
free( psz_dup );
|
|
|
|
/* Only when selected */
|
|
if( strcmp( p_access->psz_name, "vcd" ) &&
|
|
strcmp( p_access->psz_name, "svcd" ) )
|
|
return VLC_EGENERIC;
|
|
|
|
psz_dup = var_CreateGetString( p_access, "vcd" );
|
|
if( *psz_dup == '\0' )
|
|
{
|
|
free( psz_dup );
|
|
return VLC_EGENERIC;
|
|
}
|
|
}
|
|
|
|
#if defined( _WIN32 ) || defined( __OS2__ )
|
|
if( psz_dup[0] && psz_dup[1] == ':' &&
|
|
psz_dup[2] == '\\' && psz_dup[3] == '\0' ) psz_dup[2] = '\0';
|
|
#endif
|
|
|
|
/* Open VCD */
|
|
vcddev = ioctl_Open( p_this, psz_dup );
|
|
free( psz_dup );
|
|
if( !vcddev )
|
|
return VLC_EGENERIC;
|
|
|
|
/* Set up p_access */
|
|
p_access->p_sys = p_sys = calloc( 1, sizeof( access_sys_t ) );
|
|
if( unlikely(!p_sys ))
|
|
goto error;
|
|
p_sys->vcddev = vcddev;
|
|
p_sys->offset = 0;
|
|
|
|
for( size_t i = 0; i < ARRAY_SIZE(p_sys->titles); i++ )
|
|
p_sys->titles[i].seekpoints = NULL;
|
|
|
|
/* We read the Table Of Content information */
|
|
p_sys->p_toc = ioctl_GetTOC( VLC_OBJECT(p_access), p_sys->vcddev );
|
|
if( p_sys->p_toc == NULL )
|
|
{
|
|
msg_Err( p_access, "unable to count tracks" );
|
|
goto error;
|
|
}
|
|
else if( p_sys->p_toc->i_tracks <= 1 )
|
|
{
|
|
vcddev_toc_Free( p_sys->p_toc );
|
|
p_sys->p_toc = NULL;
|
|
msg_Err( p_access, "no movie tracks found" );
|
|
goto error;
|
|
}
|
|
|
|
/* The first title isn't usable */
|
|
#define USABLE_TITLES(a) (a - 1)
|
|
//p_sys->i_titles--;
|
|
|
|
for( int i = 0; i < USABLE_TITLES(p_sys->p_toc->i_tracks); i++ )
|
|
{
|
|
msg_Dbg( p_access, "title[%d] start=%d", i, p_sys->p_toc->p_sectors[1+i].i_lba );
|
|
msg_Dbg( p_access, "title[%d] end=%d", i, p_sys->p_toc->p_sectors[i+2].i_lba );
|
|
}
|
|
|
|
/* Map entry points into chapters */
|
|
if( EntryPoints( p_access ) )
|
|
{
|
|
msg_Warn( p_access, "could not read entry points, will not use them" );
|
|
}
|
|
|
|
/* Starting title/chapter and sector */
|
|
if( i_title > USABLE_TITLES(p_sys->p_toc->i_tracks) )
|
|
i_title = 0;
|
|
if( (unsigned)i_chapter >= p_sys->titles[i_title].count )
|
|
i_chapter = 0;
|
|
|
|
p_sys->i_sector = p_sys->p_toc->p_sectors[1+i_title].i_lba;
|
|
if( i_chapter > 0 )
|
|
p_sys->i_sector += p_sys->titles[i_title].seekpoints[i_chapter]
|
|
/ VCD_DATA_SIZE;
|
|
|
|
/* p_access */
|
|
p_access->pf_read = NULL;
|
|
p_access->pf_block = Block;
|
|
p_access->pf_control = Control;
|
|
p_access->pf_seek = Seek;
|
|
|
|
p_sys->i_current_title = i_title;
|
|
p_sys->i_current_seekpoint = i_chapter;
|
|
p_sys->offset = (uint64_t)(p_sys->i_sector - p_sys->p_toc->p_sectors[1+i_title].i_lba) *
|
|
VCD_DATA_SIZE;
|
|
|
|
return VLC_SUCCESS;
|
|
|
|
error:
|
|
ioctl_Close( VLC_OBJECT(p_access), vcddev );
|
|
free( p_sys );
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Close: closes vcd
|
|
*****************************************************************************/
|
|
static void Close( vlc_object_t *p_this )
|
|
{
|
|
stream_t *p_access = (stream_t *)p_this;
|
|
access_sys_t *p_sys = p_access->p_sys;
|
|
|
|
for( size_t i = 0; i < ARRAY_SIZE(p_sys->titles); i++ )
|
|
free( p_sys->titles[i].seekpoints );
|
|
|
|
vcddev_toc_Free( p_sys->p_toc );
|
|
|
|
ioctl_Close( p_this, p_sys->vcddev );
|
|
free( p_sys );
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Control:
|
|
*****************************************************************************/
|
|
static int Control( stream_t *p_access, int i_query, va_list args )
|
|
{
|
|
access_sys_t *p_sys = p_access->p_sys;
|
|
input_title_t ***ppp_title;
|
|
|
|
switch( i_query )
|
|
{
|
|
/* */
|
|
case STREAM_CAN_SEEK:
|
|
case STREAM_CAN_FASTSEEK:
|
|
case STREAM_CAN_PAUSE:
|
|
case STREAM_CAN_CONTROL_PACE:
|
|
*va_arg( args, bool* ) = true;
|
|
break;
|
|
|
|
case STREAM_GET_SIZE:
|
|
{
|
|
int i = p_sys->i_current_title;
|
|
|
|
*va_arg( args, uint64_t * ) =
|
|
(p_sys->p_toc->p_sectors[i + 2].i_lba -
|
|
p_sys->p_toc->p_sectors[i + 1].i_lba) * (uint64_t)VCD_DATA_SIZE;
|
|
break;
|
|
}
|
|
|
|
/* */
|
|
case STREAM_GET_PTS_DELAY:
|
|
*va_arg( args, vlc_tick_t * ) = VLC_TICK_FROM_MS(
|
|
var_InheritInteger(p_access, "disc-caching") );
|
|
break;
|
|
|
|
/* */
|
|
case STREAM_SET_PAUSE_STATE:
|
|
break;
|
|
|
|
case STREAM_GET_TITLE_INFO:
|
|
ppp_title = va_arg( args, input_title_t*** );
|
|
/* Duplicate title infos */
|
|
*ppp_title = vlc_alloc( USABLE_TITLES(p_sys->p_toc->i_tracks),
|
|
sizeof(input_title_t *) );
|
|
if (!*ppp_title)
|
|
return VLC_ENOMEM;
|
|
*va_arg( args, int* ) = USABLE_TITLES(p_sys->p_toc->i_tracks);
|
|
for( int i = 0; i < USABLE_TITLES(p_sys->p_toc->i_tracks); i++ )
|
|
(*ppp_title)[i] = vlc_input_title_New();
|
|
break;
|
|
|
|
case STREAM_GET_TITLE:
|
|
*va_arg( args, unsigned * ) = p_sys->i_current_title;
|
|
break;
|
|
|
|
case STREAM_GET_SEEKPOINT:
|
|
*va_arg( args, unsigned * ) = p_sys->i_current_seekpoint;
|
|
break;
|
|
|
|
case STREAM_GET_CONTENT_TYPE:
|
|
*va_arg( args, char ** ) = strdup("video/MP2P");
|
|
break;
|
|
|
|
case STREAM_SET_TITLE:
|
|
{
|
|
int i = va_arg( args, int );
|
|
if( i != p_sys->i_current_title )
|
|
{
|
|
/* Update info */
|
|
p_sys->offset = 0;
|
|
p_sys->i_current_title = i;
|
|
p_sys->i_current_seekpoint = 0;
|
|
|
|
/* Next sector to read */
|
|
p_sys->i_sector = p_sys->p_toc->p_sectors[1+i].i_lba;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case STREAM_SET_SEEKPOINT:
|
|
{
|
|
int i = va_arg( args, int );
|
|
unsigned i_title = p_sys->i_current_title;
|
|
|
|
if( p_sys->titles[i_title].count > 0 )
|
|
{
|
|
p_sys->i_current_seekpoint = i;
|
|
|
|
p_sys->i_sector = p_sys->p_toc->p_sectors[1 + i_title].i_lba +
|
|
p_sys->titles[i_title].seekpoints[i] / VCD_DATA_SIZE;
|
|
|
|
p_sys->offset = (uint64_t)(p_sys->i_sector -
|
|
p_sys->p_toc->p_sectors[1 + i_title].i_lba) * VCD_DATA_SIZE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return VLC_EGENERIC;
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Block:
|
|
*****************************************************************************/
|
|
static block_t *Block( stream_t *p_access, bool *restrict eof )
|
|
{
|
|
access_sys_t *p_sys = p_access->p_sys;
|
|
const vcddev_toc_t *p_toc = p_sys->p_toc;
|
|
int i_blocks = VCD_BLOCKS_ONCE;
|
|
block_t *p_block;
|
|
|
|
/* Check end of title */
|
|
while( p_sys->i_sector >= p_toc->p_sectors[p_sys->i_current_title + 2].i_lba )
|
|
{
|
|
if( p_sys->i_current_title + 2 >= USABLE_TITLES(p_toc->i_tracks) )
|
|
{
|
|
*eof = true;
|
|
return NULL;
|
|
}
|
|
|
|
p_sys->i_current_title++;
|
|
p_sys->i_current_seekpoint = 0;
|
|
p_sys->offset = 0;
|
|
}
|
|
|
|
/* Don't read after the end of a title */
|
|
if( p_sys->i_sector + i_blocks >=
|
|
p_toc->p_sectors[p_sys->i_current_title + 2].i_lba )
|
|
{
|
|
i_blocks = p_toc->p_sectors[p_sys->i_current_title + 2 ].i_lba - p_sys->i_sector;
|
|
}
|
|
|
|
/* Do the actual reading */
|
|
if( i_blocks < 0 || !( p_block = block_Alloc( i_blocks * VCD_DATA_SIZE ) ) )
|
|
{
|
|
msg_Err( p_access, "cannot get a new block of size: %i",
|
|
i_blocks * VCD_DATA_SIZE );
|
|
return NULL;
|
|
}
|
|
|
|
if( ioctl_ReadSectors( VLC_OBJECT(p_access), p_sys->vcddev,
|
|
p_sys->i_sector, p_block->p_buffer, i_blocks, VCD_TYPE ) < 0 )
|
|
{
|
|
msg_Err( p_access, "cannot read sector %i", p_sys->i_sector );
|
|
block_Release( p_block );
|
|
|
|
/* Try to skip one sector (in case of bad sectors) */
|
|
p_sys->offset += VCD_DATA_SIZE;
|
|
p_sys->i_sector++;
|
|
return NULL;
|
|
}
|
|
|
|
/* Update seekpoints */
|
|
for( int i_read = 0; i_read < i_blocks; i_read++ )
|
|
{
|
|
int i_title = p_sys->i_current_title;
|
|
|
|
if( p_sys->titles[i_title].count > 0 &&
|
|
p_sys->i_current_seekpoint + 1 < p_sys->titles[i_title].count &&
|
|
(p_sys->offset + i_read * VCD_DATA_SIZE) >=
|
|
p_sys->titles[i_title].seekpoints[p_sys->i_current_seekpoint + 1] )
|
|
{
|
|
msg_Dbg( p_access, "seekpoint change" );
|
|
p_sys->i_current_seekpoint++;
|
|
}
|
|
}
|
|
|
|
/* Update a few values */
|
|
p_sys->offset += p_block->i_buffer;
|
|
p_sys->i_sector += i_blocks;
|
|
|
|
return p_block;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Seek:
|
|
*****************************************************************************/
|
|
static int Seek( stream_t *p_access, uint64_t i_pos )
|
|
{
|
|
access_sys_t *p_sys = p_access->p_sys;
|
|
const vcddev_toc_t *p_toc = p_sys->p_toc;
|
|
int i_title = p_sys->i_current_title;
|
|
unsigned i_seekpoint;
|
|
|
|
/* Next sector to read */
|
|
p_sys->offset = i_pos;
|
|
p_sys->i_sector = i_pos / VCD_DATA_SIZE + p_toc->p_sectors[i_title + 1].i_lba;
|
|
|
|
/* Update current seekpoint */
|
|
for( i_seekpoint = 0; i_seekpoint < p_sys->titles[i_title].count; i_seekpoint++ )
|
|
{
|
|
if( i_seekpoint + 1 >= p_sys->titles[i_title].count ) break;
|
|
if( 0 < p_sys->titles[i_title].seekpoints[i_seekpoint + 1] &&
|
|
i_pos < p_sys->titles[i_title].seekpoints[i_seekpoint + 1] ) break;
|
|
}
|
|
|
|
if( i_seekpoint != p_sys->i_current_seekpoint )
|
|
{
|
|
msg_Dbg( p_access, "seekpoint change" );
|
|
p_sys->i_current_seekpoint = i_seekpoint;
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* EntryPoints: Reads the information about the entry points on the disc.
|
|
*****************************************************************************/
|
|
static int EntryPoints( stream_t *p_access )
|
|
{
|
|
access_sys_t *p_sys = p_access->p_sys;
|
|
const vcddev_toc_t *p_toc = p_sys->p_toc;
|
|
uint8_t sector[VCD_DATA_SIZE];
|
|
|
|
entries_sect_t entries;
|
|
int i_nb;
|
|
|
|
/* Read the entry point sector */
|
|
if( ioctl_ReadSectors( VLC_OBJECT(p_access), p_sys->vcddev,
|
|
VCD_ENTRIES_SECTOR, sector, 1, VCD_TYPE ) < 0 )
|
|
{
|
|
msg_Err( p_access, "could not read entry points sector" );
|
|
return VLC_EGENERIC;
|
|
}
|
|
memcpy( &entries, sector, CD_SECTOR_SIZE );
|
|
|
|
i_nb = GetWBE( &entries.i_entries_nb );
|
|
if( i_nb > 500 )
|
|
{
|
|
msg_Err( p_access, "invalid entry points number" );
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
if( strncmp( entries.psz_id, "ENTRYVCD", sizeof( entries.psz_id ) ) &&
|
|
strncmp( entries.psz_id, "ENTRYSVD", sizeof( entries.psz_id ) ) )
|
|
{
|
|
msg_Err( p_access, "unrecognized entry points format" );
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
for( int i = 0; i < i_nb; i++ )
|
|
{
|
|
const int i_title = BCD_TO_BIN(entries.entry[i].i_track) - 2;
|
|
const int i_sector =
|
|
(MSF_TO_LBA2( BCD_TO_BIN( entries.entry[i].msf.minute ),
|
|
BCD_TO_BIN( entries.entry[i].msf.second ),
|
|
BCD_TO_BIN( entries.entry[i].msf.frame ) ));
|
|
if( i_title < 0 ) continue; /* Should not occur */
|
|
if( i_title >= USABLE_TITLES(p_toc->i_tracks) ) continue;
|
|
|
|
msg_Dbg( p_access, "Entry[%d] title=%d sector=%d",
|
|
i, i_title, i_sector );
|
|
|
|
p_sys->titles[i_title].seekpoints = xrealloc(
|
|
p_sys->titles[i_title].seekpoints,
|
|
sizeof( uint64_t ) * (p_sys->titles[i_title].count + 1) );
|
|
p_sys->titles[i_title].seekpoints[p_sys->titles[i_title].count++] =
|
|
(i_sector - p_toc->p_sectors[i_title+1].i_lba) * VCD_DATA_SIZE;
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
|