view libmpdemux/open.c @ 15148:067f10ad6538

New section: "menc-feat-dvd-mpeg4-muxing" about how to mux a video obtained with MEncoder into different containers. Based on Rich's guide and some tips by Nico Sabi. Reviewed by The Wanderer, Dominik 'Rathann' Mierzejewski and Diego Biurrun
author gpoirier
date Wed, 13 Apr 2005 18:53:30 +0000
parents 8b9738526dd7
children de27bb6ff3ec
line wrap: on
line source


#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#include "config.h"
#include "mp_msg.h"
#include "help_mp.h"

#ifdef __FreeBSD__
#include <sys/cdrio.h>
#endif

#include "../m_option.h"
#include "stream.h"
#include "demuxer.h"
#include "mf.h"

#ifdef MPLAYER_NETWORK
#include "url.h"
#include "network.h"
extern int streaming_start( stream_t *stream, int *demuxer_type, URL_t *url);
#ifdef STREAMING_LIVE_DOT_COM
#include "demux_rtp.h"
#endif
static URL_t* url;
#endif

/// We keep these 2 for the gui atm, but they will be removed.
int dvd_title=0;
int vcd_track=0;

int dvd_chapter=1;
int dvd_last_chapter=0;
int dvd_angle=1;
char* dvd_device=NULL;
char* cdrom_device=NULL;

#ifdef USE_DVDNAV
#include "dvdnav_stream.h"
#endif

#ifdef USE_DVDREAD

#define	DVDREAD_VERSION(maj,min,micro)	((maj)*10000 + (min)*100 + (micro))

/*
 * Try to autodetect the libdvd-0.9.0 library
 * (0.9.0 removed the <dvdread/dvd_udf.h> header, and moved the two defines
 * DVD_VIDEO_LB_LEN and MAX_UDF_FILE_NAME_LEN from it to
 * <dvdread/dvd_reader.h>)
 */
#if defined(DVD_VIDEO_LB_LEN) && defined(MAX_UDF_FILE_NAME_LEN)
#define	LIBDVDREAD_VERSION	DVDREAD_VERSION(0,9,0)
#else
#define	LIBDVDREAD_VERSION	DVDREAD_VERSION(0,8,0)
#endif

char * dvd_audio_stream_types[8] =
        { "ac3","unknown","mpeg1","mpeg2ext","lpcm","unknown","dts" };

char * dvd_audio_stream_channels[6] =
	{ "mono", "stereo", "unknown", "unknown", "5.1/6.1", "5.1" };
#endif

#include "cue_read.h"



// Define function about auth the libsmbclient library
// FIXME: I really do not not is this function is properly working

#ifdef LIBSMBCLIENT

#include "libsmbclient.h"

static char smb_password[15];
static char smb_username[15];

static void smb_auth_fn(const char *server, const char *share,
             char *workgroup, int wgmaxlen, char *username, int unmaxlen,
	     char *password, int pwmaxlen)
{
  char temp[128];
  
  strcpy(temp, "LAN");				  
  if (temp[strlen(temp) - 1] == 0x0a)
    temp[strlen(temp) - 1] = 0x00;
					
  if (temp[0]) strncpy(workgroup, temp, wgmaxlen - 1);
					   
  strcpy(temp, smb_username); 
  if (temp[strlen(temp) - 1] == 0x0a)
    temp[strlen(temp) - 1] = 0x00;
						    
  if (temp[0]) strncpy(username, temp, unmaxlen - 1);
						      
  strcpy(temp, smb_password); 
  if (temp[strlen(temp) - 1] == 0x0a)
    temp[strlen(temp) - 1] = 0x00;
								
   if (temp[0]) strncpy(password, temp, pwmaxlen - 1);
}
								  

#endif

// Open a new stream  (stdin/file/vcd/url)

stream_t* open_stream(char* filename,char** options, int* file_format){
stream_t* stream=NULL;
int f=-1;
off_t len;

  // Check if playlist or unknown 
  if (*file_format != DEMUXER_TYPE_PLAYLIST){
    *file_format=DEMUXER_TYPE_UNKNOWN;
  }

if(!filename) {
   mp_msg(MSGT_OPEN,MSGL_ERR,"NULL filename, report this bug\n");
   return NULL;
}

// for opening of vcds in bincue files
if(strncmp("cue://",filename,6) == 0){
  int ret,ret2;
  char* p = filename + 6;
  vcd_track = 1;
  p = strchr(p,':');
  if(p && p[1] != '\0') {
    vcd_track = strtol(p+1,NULL,0);
    if(vcd_track < 1){ 
      mp_msg(MSGT_OPEN,MSGL_ERR,"Invalid cue track %s\n",p+1);
      return NULL;
    }
    p[0] = '\0';
  }
  f = cue_read_cue (filename + 6);
  if(p && p[1] != '\0') p[0] = ':';
  if (f == -1) return NULL;
  cue_vcd_read_toc();
  ret2=cue_vcd_get_track_end(vcd_track);
  ret=cue_vcd_seek_to_track(vcd_track);
  if(ret<0){ mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_ErrTrackSelect " (seek)\n");return NULL;}
  mp_msg(MSGT_OPEN,MSGL_V,"VCD start byte position: 0x%X  end: 0x%X\n",ret,ret2);

  stream=new_stream(f,STREAMTYPE_VCDBINCUE);
  stream->start_pos=ret;
  stream->end_pos=ret2;
  return stream;
}


//============ Open DVD title ==============
#ifdef USE_DVDNAV
if(strncmp("dvdnav://",filename,9) == 0){
    dvdnav_priv_t *dvdnav_priv;
    int event,len,tmplen=0;
    char* name = (filename[9] == '\0') ? NULL : filename + 9;
    
    stream=new_stream(-1,STREAMTYPE_DVDNAV);
    if (!stream) {
        mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_Exit_error);
        return NULL;
    }

    if(!name) name=DEFAULT_DVD_DEVICE;
    if (!(dvdnav_priv=new_dvdnav_stream(name))) {
	mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_CantOpenDVD,name);
        return NULL;
    }

    stream->priv=(void*)dvdnav_priv;
    return stream;
}
#endif
#ifdef USE_DVDREAD
if(strncmp("dvd://",filename,6) == 0){
//  int ret,ret2;
    dvd_priv_t *d;
    int ttn,pgc_id,pgn;
    dvd_reader_t *dvd;
    dvd_file_t *title;
    ifo_handle_t *vmg_file;
    tt_srpt_t *tt_srpt;
    ifo_handle_t *vts_file;
    dvd_title = filename[6] == '\0' ? 1 : strtol(filename + 6,NULL,0);
    /**
     * Open the disc.
     */
    if(!dvd_device) dvd_device=strdup(DEFAULT_DVD_DEVICE);
#ifdef SYS_DARWIN
    /* Dynamic DVD drive selection on Darwin */
    if (!strcmp(dvd_device, "/dev/rdiskN")) {
	int i;
	char *temp_device = malloc((strlen(dvd_device)+1)*sizeof(char));
	
	for (i = 1; i < 10; i++) {
	    sprintf(temp_device, "/dev/rdisk%d", i);
	    dvd = DVDOpen(temp_device);
	    if (!dvd) {
	        mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_CantOpenDVD,temp_device);
	    } else {
	        free(temp_device);
	        break;
	    }
	}
	
	if (!dvd)
	    return NULL;
    } else
#endif /* SYS_DARWIN */
    {
        dvd = DVDOpen(dvd_device);
	if( !dvd ) {
	    mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_CantOpenDVD,dvd_device);
	    return NULL;
	}
    }

    mp_msg(MSGT_OPEN,MSGL_INFO,MSGTR_DVDwait);

    /**
     * Load the video manager to find out the information about the titles on
     * this disc.
     */
    vmg_file = ifoOpen( dvd, 0 );
    if( !vmg_file ) {
        mp_msg(MSGT_OPEN,MSGL_ERR, "Can't open VMG info!\n");
        DVDClose( dvd );
        return NULL;
    }
    tt_srpt = vmg_file->tt_srpt;
    /**
     * Make sure our title number is valid.
     */
    mp_msg(MSGT_OPEN,MSGL_INFO, MSGTR_DVDnumTitles,
             tt_srpt->nr_of_srpts );
    if( dvd_title < 1 || dvd_title > tt_srpt->nr_of_srpts ) {
	mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_DVDinvalidTitle, dvd_title);
        ifoClose( vmg_file );
        DVDClose( dvd );
        return NULL;
    }
    --dvd_title; // remap 1.. -> 0..
    /**
     * Make sure the chapter number is valid for this title.
     */
    mp_msg(MSGT_OPEN,MSGL_INFO, MSGTR_DVDnumChapters,
             tt_srpt->title[dvd_title].nr_of_ptts );
    if( dvd_chapter<1 || dvd_chapter>tt_srpt->title[dvd_title].nr_of_ptts ) {
	mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_DVDinvalidChapter, dvd_chapter);
        ifoClose( vmg_file );
        DVDClose( dvd );
        return NULL;
    }
    if( dvd_last_chapter>0 ) {
	if ( dvd_last_chapter<dvd_chapter || dvd_last_chapter>tt_srpt->title[dvd_title].nr_of_ptts ) {
	    mp_msg(MSGT_OPEN,MSGL_ERR, "Invalid DVD last chapter number: %d\n", dvd_last_chapter);
	    ifoClose( vmg_file );
	    DVDClose( dvd );
	    return NULL;
	}
    }
    --dvd_chapter; // remap 1.. -> 0..
    /* XXX No need to remap dvd_last_chapter */
    /**
     * Make sure the angle number is valid for this title.
     */
    mp_msg(MSGT_OPEN,MSGL_INFO, MSGTR_DVDnumAngles,
             tt_srpt->title[dvd_title].nr_of_angles );
    if( dvd_angle<1 || dvd_angle>tt_srpt->title[dvd_title].nr_of_angles ) {
	mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_DVDinvalidAngle, dvd_angle);
        ifoClose( vmg_file );
        DVDClose( dvd );
        return NULL;
    }
    --dvd_angle; // remap 1.. -> 0..
    /**
     * Load the VTS information for the title set our title is in.
     */
    vts_file = ifoOpen( dvd, tt_srpt->title[dvd_title].title_set_nr );
    if( !vts_file ) {
	mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_DVDnoIFO,
                 tt_srpt->title[dvd_title].title_set_nr );
        ifoClose( vmg_file );
        DVDClose( dvd );
        return NULL;
    }
    /**
     * We've got enough info, time to open the title set data.
     */
    title = DVDOpenFile( dvd, tt_srpt->title[dvd_title].title_set_nr,
                         DVD_READ_TITLE_VOBS );
    if( !title ) {
	mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_DVDnoVOBs,
                 tt_srpt->title[dvd_title].title_set_nr );
        ifoClose( vts_file );
        ifoClose( vmg_file );
        DVDClose( dvd );
        return NULL;
    }

    mp_msg(MSGT_OPEN,MSGL_INFO, MSGTR_DVDopenOk);
    // store data
    d=malloc(sizeof(dvd_priv_t)); memset(d,0,sizeof(dvd_priv_t));
    d->dvd=dvd;
    d->title=title;
    d->vmg_file=vmg_file;
    d->tt_srpt=tt_srpt;
    d->vts_file=vts_file;

    /**
     * Check number of audio channels and types
     */
    {
     int ac3aid = 128;
     int mpegaid = 0;
     int pcmaid = 160;
     
     d->nr_of_channels=0;
     
     if ( vts_file->vts_pgcit ) 
      {
       int i;
       for ( i=0;i<8;i++ )
        if ( vts_file->vts_pgcit->pgci_srp[0].pgc->audio_control[i] & 0x8000 )
	 {
	  audio_attr_t * audio = &vts_file->vtsi_mat->vts_audio_attr[i];
	  int language = 0;
	  char tmp[] = "unknown";
	  
	  if ( audio->lang_type == 1 ) 
	   {
	    language=audio->lang_code;
	    tmp[0]=language>>8;
	    tmp[1]=language&0xff;
	    tmp[2]=0;
	   }
	  
          d->audio_streams[d->nr_of_channels].language=language;
          d->audio_streams[d->nr_of_channels].id=0;
	  switch ( audio->audio_format )
	   {
	    case 0: // ac3
	  	    d->audio_streams[d->nr_of_channels].id=ac3aid;
		    ac3aid++;
		    break;
	    case 6: // dts
	            d->audio_streams[d->nr_of_channels].id=ac3aid+8;
		    ac3aid++;
		    break;
	    case 2: // mpeg layer 1/2/3
	    case 3: // mpeg2 ext
	            d->audio_streams[d->nr_of_channels].id=mpegaid;
		    mpegaid++;
		    break;
	    case 4: // lpcm
	            d->audio_streams[d->nr_of_channels].id=pcmaid;
		    pcmaid++;
		    break;
	   }

	  d->audio_streams[d->nr_of_channels].type=audio->audio_format;
	  // Pontscho: to my mind, tha channels:
	  //  1 - stereo
	  //  5 - 5.1
	  d->audio_streams[d->nr_of_channels].channels=audio->channels;
          mp_msg(MSGT_OPEN,MSGL_V,"[open] audio stream: %d audio format: %s (%s) language: %s aid: %d\n",
	    d->nr_of_channels,
            dvd_audio_stream_types[ audio->audio_format ],
	    dvd_audio_stream_channels[ audio->channels ],
	    tmp,
	    d->audio_streams[d->nr_of_channels].id
	    );
	  if (identify)
	  {
	    mp_msg(MSGT_GLOBAL, MSGL_INFO, "ID_AUDIO_ID=%d\n", d->audio_streams[d->nr_of_channels].id);
	    if (language && tmp[0])
	      mp_msg(MSGT_GLOBAL, MSGL_INFO, "ID_AID_%d_LANG=%s\n", d->audio_streams[d->nr_of_channels].id, tmp);
	  }
	    
	  d->nr_of_channels++;
	 }
      }
     mp_msg(MSGT_OPEN,MSGL_V,"[open] number of audio channels on disk: %d.\n",d->nr_of_channels );
    }

    /**
     * Check number of subtitles and language
     */
    {
     int i;

     d->nr_of_subtitles=0;
     for ( i=0;i<32;i++ )
      if ( vts_file->vts_pgcit->pgci_srp[0].pgc->subp_control[i] & 0x80000000 )
       {
        subp_attr_t * subtitle = &vts_file->vtsi_mat->vts_subp_attr[i];
	int language = 0;
	char tmp[] = "unknown";
	
	if ( subtitle->type == 1 )
	 {
	  language=subtitle->lang_code;
	  tmp[0]=language>>8;
	  tmp[1]=language&0xff;
	  tmp[2]=0;
	 }
	 
	d->subtitles[ d->nr_of_subtitles ].language=language;
	d->subtitles[ d->nr_of_subtitles ].id=d->nr_of_subtitles;
	
        mp_msg(MSGT_OPEN,MSGL_V,"[open] subtitle ( sid ): %d language: %s\n",
	  d->nr_of_subtitles,
	  tmp
	  );
        if (identify)
        {
	  mp_msg(MSGT_GLOBAL, MSGL_INFO, "ID_SUBTITLE_ID=%d\n", d->nr_of_subtitles);
	  if (language && tmp[0])
	    mp_msg(MSGT_GLOBAL, MSGL_INFO, "ID_SID_%d_LANG=%s\n", d->nr_of_subtitles, tmp);
        }
        d->nr_of_subtitles++;
       }
     mp_msg(MSGT_OPEN,MSGL_V,"[open] number of subtitles on disk: %d\n",d->nr_of_subtitles );
    }

    /**
     * Determine which program chain we want to watch.  This is based on the
     * chapter number.
     */
    ttn = tt_srpt->title[ dvd_title ].vts_ttn; // local
    pgc_id = vts_file->vts_ptt_srpt->title[ttn-1].ptt[dvd_chapter].pgcn; // local
    pgn    = vts_file->vts_ptt_srpt->title[ttn-1].ptt[dvd_chapter].pgn;  // local
    d->cur_pgc = vts_file->vts_pgcit->pgci_srp[pgc_id-1].pgc;
    d->cur_cell = d->cur_pgc->program_map[pgn-1] - 1; // start playback here
    d->packs_left=-1;      // for Navi stuff
    d->angle_seek=0;
    /* XXX dvd_last_chapter is in the range 1..nr_of_ptts */
    if ( dvd_last_chapter > 0 && dvd_last_chapter < tt_srpt->title[dvd_title].nr_of_ptts ) {
	pgn=vts_file->vts_ptt_srpt->title[ttn-1].ptt[dvd_last_chapter].pgn;
	d->last_cell=d->cur_pgc->program_map[pgn-1] - 1;
    }
    else
	d->last_cell=d->cur_pgc->nr_of_cells;
    
    if( d->cur_pgc->cell_playback[d->cur_cell].block_type == BLOCK_TYPE_ANGLE_BLOCK ) d->cur_cell+=dvd_angle;
    d->cur_pack = d->cur_pgc->cell_playback[ d->cur_cell ].first_sector;
    d->cell_last_pack=d->cur_pgc->cell_playback[ d->cur_cell ].last_sector;
    mp_msg(MSGT_DVD,MSGL_V, "DVD start cell: %d  pack: 0x%X-0x%X  \n",d->cur_cell,d->cur_pack,d->cell_last_pack);

    // ... (unimplemented)
//    return NULL;
  stream=new_stream(-1,STREAMTYPE_DVD);
  stream->start_pos=(off_t)d->cur_pack*2048;
  stream->end_pos=(off_t)(d->cur_pgc->cell_playback[d->last_cell-1].last_sector)*2048;
  mp_msg(MSGT_DVD,MSGL_V,"DVD start=%d end=%d  \n",d->cur_pack,d->cur_pgc->cell_playback[d->last_cell-1].last_sector);
  stream->priv=(void*)d;
  return stream;
}
#endif


#ifdef MPLAYER_NETWORK
#ifdef STREAMING_LIVE_DOT_COM
  // Check for a SDP file:
  if (strncmp("sdp://",filename,6) == 0) {
       filename += 6;
#if defined(__CYGWIN__) || defined(__MINGW32__)
       f=open(filename,O_RDONLY|O_BINARY);
#else
       f=open(filename,O_RDONLY);
#endif
       if(f<0){ mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_FileNotFound,filename);return NULL; }

       len=lseek(f,0,SEEK_END); lseek(f,0,SEEK_SET);
       if (len == -1)
           return NULL;

#ifdef _LARGEFILE_SOURCE
	 mp_msg(MSGT_OPEN,MSGL_V,"File size is %lld bytes\n", (long long)len);
#else
	 mp_msg(MSGT_OPEN,MSGL_V,"File size is %u bytes\n", (unsigned int)len);
#endif
	 return stream_open_sdp(f, len, file_format);
  }
#endif

  // FIXME: to avoid nonsense error messages...
  if (strncmp("tv://", filename, 5) && strncmp("mf://", filename, 5) &&
    strncmp("vcd://", filename, 6) && strncmp("dvb://", filename, 6) &&
    strncmp("cdda://", filename, 7) && strncmp("cddb://", filename, 7) &&
    strncmp("mpst://", filename, 7) && strncmp("tivo://", filename, 7) &&
    strstr(filename, "://")) {
     url = url_new(filename);
    }
  if(url) {
	if (strcmp(url->protocol, "smb")==0){
#ifdef LIBSMBCLIENT
	    
	    // we need init of libsmbclient
            int err;
	    
	    // FIXME: HACK: make the username/password global varaibles
	    // so the auth_fn function should grab it ...
	    // i cannot thing other way...
	    err = smbc_init(smb_auth_fn, 10);  	/* Initialize things */
	                        	        // libsmbclient using				
	    if (err < 0) {
        	mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_SMBInitError,err);
	    	return NULL;
	    }
	    f=smbc_open(filename, O_RDONLY, 0666);    
	    
	    // cannot open the file, outputs that
	    // MSGTR_FileNotFound -> MSGTR_SMBFileNotFound
    	    if(f<0){ 
		mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_SMBFileNotFound,filename);
		return NULL; 
	    }
	    len=smbc_lseek(f,0,SEEK_END); 
	    smbc_lseek(f,0,SEEK_SET);
	    // FIXME: I really wonder is such situation -> but who cares ;)
//	    if (len == -1)	
//    		   return new_stream(f,STREAMTYPE_STREAM); // open as stream
	    url_free(url);
	    url = NULL;
            stream=new_stream(f,STREAMTYPE_SMB);
    	    stream->end_pos=len;
	    return stream;
#else
	    mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_SMBNotCompiled);
	    return NULL;
#endif
	}
        stream=new_stream(f,STREAMTYPE_STREAM);
    if (strcmp(url->protocol, "ftp")) { // ftp is handled somewhere else
	if( streaming_start( stream, file_format, url )<0){
          mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_UnableOpenURL, filename);
	  url_free(url);
	  url = NULL;
	  return NULL;
	} else {
        mp_msg(MSGT_OPEN,MSGL_INFO,MSGTR_ConnToServer, url->hostname );
	url_free(url);
	url = NULL;
	return stream;
	}
  }
  }
#endif

//============ Open STDIN or plain FILE ============

  return open_stream_full(filename,STREAM_READ,options,file_format);
}

int dvd_parse_chapter_range(m_option_t *conf, const char *range){
  const char *s;
  char *t;
  if (!range)
    return M_OPT_MISSING_PARAM;
  s = range;
  dvd_chapter = 1;
  dvd_last_chapter = 0;
  if (*range && isdigit(*range)) {
    dvd_chapter = strtol(range, &s, 10);
    if (range == s) {
      mp_msg(MSGT_OPEN, MSGL_ERR, "Invalid chapter range specification %s\n", range);
      return M_OPT_INVALID;
    }
  }
  if (*s == 0)
    return 0;
  else if (*s != '-') {
    mp_msg(MSGT_OPEN, MSGL_ERR, "Invalid chapter range specification %s\n", range);
    return M_OPT_INVALID;
  }
  ++s;
  if (*s == 0)
      return 0;
  if (! isdigit(*s)) {
    mp_msg(MSGT_OPEN, MSGL_ERR, "Invalid chapter range specification %s\n", range);
    return M_OPT_INVALID;
  }
  dvd_last_chapter = strtol(s, &t, 10);
  if (s == t || *t)  {
    mp_msg(MSGT_OPEN, MSGL_ERR, "Invalid chapter range specification %s\n", range);
    return M_OPT_INVALID;
  }
  return 0;
}

#ifdef USE_DVDREAD
int dvd_chapter_from_cell(dvd_priv_t* dvd,int title,int cell)
{
  pgc_t * cur_pgc;
  ptt_info_t* ptt;
  int chapter = cell;
  int pgc_id,pgn;
  if(title < 0 || cell < 0){
    return 0;
  }
  /* for most DVD's chapter == cell */
  /* but there are more complecated cases... */
  if(chapter >= dvd->vmg_file->tt_srpt->title[title].nr_of_ptts){
    chapter = dvd->vmg_file->tt_srpt->title[title].nr_of_ptts-1;
  }
  title = dvd->tt_srpt->title[title].vts_ttn-1;
  ptt = dvd->vts_file->vts_ptt_srpt->title[title].ptt;
  while(chapter >= 0){
    pgc_id = ptt[chapter].pgcn;
    pgn = ptt[chapter].pgn;
    cur_pgc = dvd->vts_file->vts_pgcit->pgci_srp[pgc_id-1].pgc;
    if(cell >= cur_pgc->program_map[pgn-1]-1){
      return chapter;
    }
    --chapter;
  }
  /* didn't find a chapter ??? */
  return chapter;
}

int dvd_aid_from_lang(stream_t *stream, unsigned char* lang){
dvd_priv_t *d=stream->priv;
int code,i;
if(lang){
  while(strlen(lang)>=2){
    code=lang[1]|(lang[0]<<8);
    for(i=0;i<d->nr_of_channels;i++){
	if(d->audio_streams[i].language==code){
	    mp_msg(MSGT_OPEN,MSGL_INFO,"Selected DVD audio channel: %d language: %c%c\n",
		d->audio_streams[i].id, lang[0],lang[1]);
	    return d->audio_streams[i].id;
	}
//	printf("%X != %X  (%c%c)\n",code,d->audio_streams[i].language,lang[0],lang[1]);
    }
    lang+=2; while (lang[0]==',' || lang[0]==' ') ++lang;
  }
  mp_msg(MSGT_OPEN,MSGL_WARN,"No matching DVD audio language found!\n");
}
return -1;
}

int dvd_number_of_subs(stream_t *stream)
{
  dvd_priv_t *d;
  if (!stream) return -1;
  d = stream->priv;
  if (!d) return -1;
  return d->nr_of_subtitles;
}

int dvd_lang_from_sid(stream_t *stream, int id)
{
  dvd_priv_t *d;
  if (!stream) return 0;
  d = stream->priv;
  if (!d) return 0;
  if (id >= d->nr_of_subtitles) return 0;
  return d->subtitles[id].language;
}

int dvd_sid_from_lang(stream_t *stream, unsigned char* lang){
dvd_priv_t *d=stream->priv;
int code,i;
  while(lang && strlen(lang)>=2){
    code=lang[1]|(lang[0]<<8);
    for(i=0;i<d->nr_of_subtitles;i++){
	if(d->subtitles[i].language==code){
	    mp_msg(MSGT_OPEN,MSGL_INFO,"Selected DVD subtitle channel: %d language: %c%c\n",
		d->subtitles[i].id, lang[0],lang[1]);
	    return d->subtitles[i].id;
	}
    }
    lang+=2; while (lang[0]==',' || lang[0]==' ') ++lang;
  }
  mp_msg(MSGT_OPEN,MSGL_WARN,"No matching DVD subtitle language found!\n");
  return -1;
}

static int dvd_next_cell(dvd_priv_t *d){
    int next_cell=d->cur_cell;

    mp_msg(MSGT_DVD,MSGL_V, "dvd_next_cell: next1=0x%X  \n",next_cell);
    
    if( d->cur_pgc->cell_playback[ next_cell ].block_type
                                        == BLOCK_TYPE_ANGLE_BLOCK ) {
	    while(next_cell<d->last_cell){
                if( d->cur_pgc->cell_playback[next_cell].block_mode
                                          == BLOCK_MODE_LAST_CELL ) break;
		++next_cell;
            }
    }
    mp_msg(MSGT_DVD,MSGL_V, "dvd_next_cell: next2=0x%X  \n",next_cell);
    
    ++next_cell;
    if(next_cell>=d->last_cell) return -1; // EOF
    if( d->cur_pgc->cell_playback[next_cell].block_type == BLOCK_TYPE_ANGLE_BLOCK ){
	next_cell+=dvd_angle;
	if(next_cell>=d->last_cell) return -1; // EOF
    }
    mp_msg(MSGT_DVD,MSGL_V, "dvd_next_cell: next3=0x%X  \n",next_cell);
    return next_cell;
}

int dvd_read_sector(dvd_priv_t *d,unsigned char* data){
    int len;
    
    if(d->packs_left==0){
            /**
             * If we're not at the end of this cell, we can determine the next
             * VOBU to display using the VOBU_SRI information section of the
             * DSI.  Using this value correctly follows the current angle,
             * avoiding the doubled scenes in The Matrix, and makes our life
             * really happy.
             *
             * Otherwise, we set our next address past the end of this cell to
             * force the code above to go to the next cell in the program.
             */
            if( d->dsi_pack.vobu_sri.next_vobu != SRI_END_OF_CELL ) {
                d->cur_pack= d->dsi_pack.dsi_gi.nv_pck_lbn +
		( d->dsi_pack.vobu_sri.next_vobu & 0x7fffffff );
		mp_msg(MSGT_DVD,MSGL_DBG2, "Navi  new pos=0x%X  \n",d->cur_pack);
            } else {
		// end of cell! find next cell!
		mp_msg(MSGT_DVD,MSGL_V, "--- END OF CELL !!! ---\n");
		d->cur_pack=d->cell_last_pack+1;
            }
    }

read_next:

    if(d->cur_pack>d->cell_last_pack){
	// end of cell!
	int next=dvd_next_cell(d);
	if(next>=0){
	    d->cur_cell=next;

//    if( d->cur_pgc->cell_playback[d->cur_cell].block_type 
//	== BLOCK_TYPE_ANGLE_BLOCK ) d->cur_cell+=dvd_angle;
    d->cur_pack = d->cur_pgc->cell_playback[ d->cur_cell ].first_sector;
    d->cell_last_pack=d->cur_pgc->cell_playback[ d->cur_cell ].last_sector;
    mp_msg(MSGT_DVD,MSGL_V, "DVD next cell: %d  pack: 0x%X-0x%X  \n",d->cur_cell,d->cur_pack,d->cell_last_pack);
	    
	} else return -1; // EOF
    }

    len = DVDReadBlocks( d->title, d->cur_pack, 1, data );
    if(!len) return -1; //error
    
    if(data[38]==0 && data[39]==0 && data[40]==1 && data[41]==0xBF &&
       data[1024]==0 && data[1025]==0 && data[1026]==1 && data[1027]==0xBF){
	// found a Navi packet!!!
#if LIBDVDREAD_VERSION >= DVDREAD_VERSION(0,9,0)
        navRead_DSI( &d->dsi_pack, &(data[ DSI_START_BYTE ]) );
#else
        navRead_DSI( &d->dsi_pack, &(data[ DSI_START_BYTE ]), sizeof(dsi_t) );
#endif
	if(d->cur_pack != d->dsi_pack.dsi_gi.nv_pck_lbn ){
	    mp_msg(MSGT_DVD,MSGL_V, "Invalid NAVI packet! lba=0x%X  navi=0x%X  \n",
		d->cur_pack,d->dsi_pack.dsi_gi.nv_pck_lbn);
	} else {
	    // process!
    	    d->packs_left = d->dsi_pack.dsi_gi.vobu_ea;
	    mp_msg(MSGT_DVD,MSGL_DBG2, "Found NAVI packet! lba=0x%X  len=%d  \n",d->cur_pack,d->packs_left);
	    //navPrint_DSI(&d->dsi_pack);
	    mp_msg(MSGT_DVD,MSGL_DBG3,"\r### CELL %d: Navi: %d/%d  IFO: %d/%d   \n",d->cur_cell,
		d->dsi_pack.dsi_gi.vobu_c_idn,d->dsi_pack.dsi_gi.vobu_vob_idn,
		d->cur_pgc->cell_position[d->cur_cell].cell_nr,
		d->cur_pgc->cell_position[d->cur_cell].vob_id_nr);

	    if(d->angle_seek){
		int i,skip=0;
#if defined(__GNUC__) && ( defined(__sparc__) || defined(hpux) )
		// workaround for a bug in the sparc/hpux version of gcc 2.95.X ... 3.2,
		// it generates incorrect code for unaligned access to a packed
		// structure member, resulting in an mplayer crash with a SIGBUS
		// signal.
		//
		// See also gcc problem report PR c/7847:
		// http://gcc.gnu.org/cgi-bin/gnatsweb.pl?database=gcc&cmd=view+audit-trail&pr=7847
		for(i=0;i<9;i++){	// check if all values zero:
		    typeof(d->dsi_pack.sml_agli.data[i].address) tmp_addr;
		    memcpy(&tmp_addr,&d->dsi_pack.sml_agli.data[i].address,sizeof(tmp_addr));
		    if((skip=tmp_addr)!=0) break;
		}
#else
		for(i=0;i<9;i++)	// check if all values zero:
		    if((skip=d->dsi_pack.sml_agli.data[i].address)!=0) break;
#endif
		if(skip){
		    // sml_agli table has valid data (at least one non-zero):
		    d->cur_pack=d->dsi_pack.dsi_gi.nv_pck_lbn+
			d->dsi_pack.sml_agli.data[dvd_angle].address;
		    d->angle_seek=0;
		    mp_msg(MSGT_DVD,MSGL_V, "Angle-seek synced using sml_agli map!  new_lba=0x%X  \n",d->cur_pack);
		} else {
		    // check if we're in the right cell, jump otherwise:
		    if( (d->dsi_pack.dsi_gi.vobu_c_idn==d->cur_pgc->cell_position[d->cur_cell].cell_nr) &&
		        (d->dsi_pack.dsi_gi.vobu_vob_idn==d->cur_pgc->cell_position[d->cur_cell].vob_id_nr) ){
			d->angle_seek=0;
			mp_msg(MSGT_DVD,MSGL_V, "Angle-seek synced by cell/vob IDN search!  \n");
		    } else {
			// wrong angle, skip this vobu:
			d->cur_pack=d->dsi_pack.dsi_gi.nv_pck_lbn+
			    d->dsi_pack.dsi_gi.vobu_ea;
			d->angle_seek=2; // DEBUG
		    }
		}
	    }
	}
	++d->cur_pack;
	goto read_next;
    }
    
    ++d->cur_pack;
    if(d->packs_left>=0) --d->packs_left;
    
    if(d->angle_seek){
	if(d->angle_seek==2) mp_msg(MSGT_DVD,MSGL_V, "!!! warning! reading packet while angle_seek !!!\n");
	goto read_next; // searching for Navi packet
    }

    return d->cur_pack-1;
}

void dvd_seek(dvd_priv_t *d,int pos){
    d->packs_left=-1;
    d->cur_pack=pos;
    
// check if we stay in current cell (speedup things, and avoid angle skip)
if(d->cur_pack>d->cell_last_pack ||
   d->cur_pack<d->cur_pgc->cell_playback[ d->cur_cell ].first_sector){

    // ok, cell change, find the right cell!
    d->cur_cell=0;
    if( d->cur_pgc->cell_playback[d->cur_cell].block_type 
	== BLOCK_TYPE_ANGLE_BLOCK ) d->cur_cell+=dvd_angle;

  while(1){
    int next;
    d->cell_last_pack=d->cur_pgc->cell_playback[ d->cur_cell ].last_sector;
    if(d->cur_pack<d->cur_pgc->cell_playback[ d->cur_cell ].first_sector){
	d->cur_pack=d->cur_pgc->cell_playback[ d->cur_cell ].first_sector;
	break;
    }
    if(d->cur_pack<=d->cell_last_pack) break; // ok, we find it! :)
    next=dvd_next_cell(d);
    if(next<0){
//	d->cur_pack=d->cell_last_pack+1;
	break; // we're after the last cell
    }
    d->cur_cell=next;
  }

}

mp_msg(MSGT_DVD,MSGL_V, "DVD Seek! lba=0x%X  cell=%d  packs: 0x%X-0x%X  \n",
    d->cur_pack,d->cur_cell,d->cur_pgc->cell_playback[ d->cur_cell ].first_sector,d->cell_last_pack);

// if we're in interleaved multi-angle cell, find the right angle chain!
// (read Navi block, and use the seamless angle jump table)
d->angle_seek=1;

}

void dvd_close(dvd_priv_t *d) {
  ifoClose(d->vts_file);
  ifoClose(d->vmg_file);
  DVDCloseFile(d->title);
  DVDClose(d->dvd);
  dvd_chapter = 1;
  dvd_last_chapter = 0;
}

#endif