/* * Copyright (C) 2000-2001 the xine project * * This file is part of xine, a unix video player. * * xine is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * xine 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * $Id: input_dvd.c,v 1.49 2002/05/25 19:19:17 siggi Exp $ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_CDIO_H # include #endif #ifdef HAVE_LINUX_CDROM_H # include #elif defined __FreeBSD__ # include "sys/dvdio.h" #endif #if ! defined (HAVE_LINUX_CDROM_H) && ! defined (HAVE_SYS_CDIO_H) #error "you need to add dvd support for your platform to input_dvd.c and configure.in" #endif #include "xine_internal.h" #include "xineutils.h" #include "input_plugin.h" #include "dvd_udf.h" #include "read_cache.h" extern int errno; #ifdef __GNUC__ #define LOG_MSG_STDERR(xine, message, args...) { \ xine_log(xine, XINE_LOG_MSG, message, ##args); \ fprintf(stderr, message, ##args); \ } #define LOG_MSG(xine, message, args...) { \ xine_log(xine, XINE_LOG_MSG, message, ##args); \ printf(message, ##args); \ } #else #define LOG_MSG_STDERR(xine, ...) { \ xine_log(xine, XINE_LOG_MSG, __VA_ARGS__); \ fprintf(stderr, __VA_ARGS__); \ } #define LOG_MSG(xine, ...) { \ xine_log(xine, XINE_LOG_MSG, __VA_ARGS__); \ printf(__VA_ARGS__); \ } #endif #if defined(__sun) #define RDVD "/vol/dev/aliases/cdrom0" #define DVD RDVD #else #define DVD "/dev/dvd" #define RDVD "/dev/rdvd" #endif typedef struct { input_plugin_t input_plugin; xine_t *xine; char *mrl; config_values_t *config; int dvd_fd; int raw_fd; read_cache_t *read_cache; off_t file_size; off_t file_size_left; int file_lbstart; int file_lbcur; int gVTSMinor; int gVTSMajor; const char *device; const char *raw_device; /* * udf dir function */ #define MAX_DIR_ENTRIES 250 char *filelist[MAX_DIR_ENTRIES]; char *filelist2[MAX_DIR_ENTRIES]; int mrls_allocated_entries; mrl_t **mrls; } dvd_input_plugin_t; /* ***************************************************************** */ /* Private functions */ /* ***************************************************************** */ /* * Callbacks for configuratoin changes. */ static void device_change_cb(void *data, cfg_entry_t *cfg) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) data; this->device = strdup(cfg->str_value); } static void rawdevice_change_cb(void *data, cfg_entry_t *cfg) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) data; this->raw_device = strdup(cfg->str_value); } static int openDrive (dvd_input_plugin_t *this) { this->dvd_fd = open(this->device, O_RDONLY /* | O_NONBLOCK */ ); if (this->dvd_fd < 0) { LOG_MSG(this->xine, _("input_dvd: unable to open dvd drive (%s): %s\n"), this->device, strerror(errno)); return -1; } this->raw_fd = open(this->raw_device, O_RDONLY /* | O_NONBLOCK */ ); if (this->raw_fd < 0) { this->raw_fd = this->dvd_fd; } read_cache_set_fd (this->read_cache, this->raw_fd); return this->raw_fd; } static void closeDrive (dvd_input_plugin_t *this) { if (this->dvd_fd < 0) return; close (this->dvd_fd); if (this->raw_fd != this->dvd_fd) close (this->raw_fd); this->dvd_fd = -1; } #ifdef __sun #include #include #include /* SCSI mmc3 DVD Commands */ #define GPCMD_READ_DVD_STRUCTURE 0xad #define GPCMD_SEND_DVD_STRUCTURE 0xad #define GPCMD_REPORT_KEY 0xa4 #define GPCMD_SEND_KEY 0xa3 /* DVD struct types */ #define DVD_STRUCT_PHYSICAL 0x00 #define DVD_STRUCT_COPYRIGHT 0x01 #define DVD_STRUCT_DISCKEY 0x02 #define DVD_STRUCT_BCA 0x03 #define DVD_STRUCT_MANUFACT 0x04 struct dvd_copyright { uint8_t type; uint8_t layer_num; uint8_t cpst; uint8_t rmi; }; typedef union { uint8_t type; /* struct dvd_physical physical; */ struct dvd_copyright copyright; /* struct dvd_disckey disckey; struct dvd_bca bca; struct dvd_manufact manufact; */ } dvd_struct; /* * Read DVD "Copyright Structure" from DVD Drive */ static int dvd_read_copyright(dvd_input_plugin_t *this, dvd_struct *s) { struct uscsi_cmd sc; union scsi_cdb rs_cdb; uint8_t buf[8]; memset(&rs_cdb, 0, sizeof(rs_cdb)); rs_cdb.scc_cmd = GPCMD_READ_DVD_STRUCTURE; rs_cdb.cdb_opaque[6] = s->copyright.layer_num; rs_cdb.cdb_opaque[7] = s->type; rs_cdb.cdb_opaque[8] = (sizeof(buf) >> 8) & 0xff; rs_cdb.cdb_opaque[9] = sizeof(buf) & 0xff; memset(&sc, 0, sizeof(sc)); sc.uscsi_cdb = (caddr_t)&rs_cdb; sc.uscsi_cdblen = 12; sc.uscsi_bufaddr = buf; sc.uscsi_buflen = sizeof(buf); sc.uscsi_flags = USCSI_ISOLATE|USCSI_READ; sc.uscsi_timeout = 15; memset(buf, 0, sizeof(buf)); if (ioctl(this->raw_fd, USCSICMD, &sc)) { LOG_MSG(this->xine, _("USCSICMD dvd_read_copyright: %s"), strerror(errno)); return -1; } if (sc.uscsi_status) { LOG_MSG_STDERR(this->xine, _("bad status: READ DVD STRUCTURE (copyright)\n")); return -1; } s->copyright.cpst = buf[4]; s->copyright.rmi = buf[5]; return 0; } /* * Check the environment, if we're running under sun's * vold/rmmount control. */ static void check_solaris_vold_device(dvd_input_plugin_t *this) { char *volume_device; char *volume_name; char *volume_action; char *device; struct stat stb; if ((volume_device = getenv("VOLUME_DEVICE")) != NULL && (volume_name = getenv("VOLUME_NAME")) != NULL && (volume_action = getenv("VOLUME_ACTION")) != NULL && strcmp(volume_action, "insert") == 0) { device = malloc(strlen(volume_device) + strlen(volume_name) + 2); if (device == NULL) return; sprintf(device, "%s/%s", volume_device, volume_name); if (stat(device, &stb) != 0 || !S_ISCHR(stb.st_mode)) { free(device); return; } this->device = this->raw_device = device; } } #endif /* * try to open dvd and prepare to read >filename< * * returns lbnum on success, 0 otherwise */ static int openDVDFile (dvd_input_plugin_t *this, char *filename, off_t *size) { char str[256]; int lbnum; int encrypted=0; if (openDrive(this) < 0) { LOG_MSG(this->xine, _("input_dvd: cannot open dvd drive >%s<\n"), this->device); return 0; } #if defined HAVE_LINUX_CDROM_H { dvd_struct dvd; dvd.copyright.type = DVD_STRUCT_COPYRIGHT; dvd.copyright.layer_num = 0; if (ioctl (this->dvd_fd, DVD_READ_STRUCT, &dvd) < 0) { LOG_MSG(this->xine, _("input_dvd: Could not read Copyright Structure\n")); return 0; } encrypted = (dvd.copyright.cpst != 0) ; } #elif defined __FreeBSD__ { struct dvd_struct dvd; dvd.format = DVD_STRUCT_COPYRIGHT; dvd.layer_num = 0; if (ioctl(this->dvd_fd, DVDIOCREADSTRUCTURE, &dvd) < 0) { LOG_MSG(this->xine, _("input_dvd: Could not read Copyright Structure\n")); return 0; } encrypted = (dvd.cpst != 0); } #elif defined __sun { dvd_struct dvd; dvd.copyright.type = DVD_STRUCT_COPYRIGHT; dvd.copyright.layer_num = 0; if (dvd_read_copyright(this, &dvd) < 0) { LOG_MSG(this->xine, _("input_dvd: Could not read Copyright Structure.\n" " Assuming disk is not encrypted.\n")); } else encrypted = (dvd.copyright.cpst != 0); } #endif if( encrypted ) { LOG_MSG(this->xine, _("\ninput_dvd: Sorry, this plugin doesn't play encrypted DVDs. The legal status\n" " of CSS decryption is unclear and we can't provide such code.\n" " Please check http://dvd.sf.net for more information.\n")); return 0; } snprintf (str, sizeof(str), "/VIDEO_TS/%s", filename); if (!(lbnum = UDFFindFile(this->dvd_fd, str, size))) { LOG_MSG(this->xine, _("input_dvd: cannot open file >%s<\n"), filename); closeDrive (this); return 0; } lseek (this->raw_fd, lbnum * (off_t) DVD_VIDEO_LB_LEN, SEEK_SET) ; return lbnum; } /* ***************************************************************** */ /* END OF PRIVATES */ /* ***************************************************************** */ /* * */ static uint32_t dvd_plugin_get_capabilities (input_plugin_t *this) { return INPUT_CAP_SEEKABLE | INPUT_CAP_PREVIEW | INPUT_CAP_BLOCK | INPUT_CAP_AUTOPLAY | INPUT_CAP_GET_DIR; } /* * */ static int dvd_plugin_open (input_plugin_t *this_gen, char *mrl) { char *filename; dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; this->mrl = mrl; /* * do we handle this kind of MRL ? */ if (strncasecmp (mrl, "dvd://", 6)) return 0; filename = (char *) &mrl[6]; sscanf (filename, "VTS_%d_%d.VOB", &this->gVTSMajor, &this->gVTSMinor); this->file_lbstart = openDVDFile (this, filename, &this->file_size) ; this->file_lbcur = this->file_lbstart; if (!this->file_lbstart) { LOG_MSG(this->xine, _("input_dvd: Unable to find >%s< on dvd.\n"), filename); return 0; } this->file_size_left = this->file_size; return 1 ; } static int dvd_plugin_is_branch_possible(input_plugin_t *this_gen, char *nextmrl ) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; char *mrl; if (strncasecmp (nextmrl, "dvd://", 6)) return 0; mrl = this->mrl; mrl += 6; nextmrl += 6; if( strncasecmp (mrl, "VTS_", 4) || strncasecmp (nextmrl, "VTS_", 4) ) return 0; return 1; } static off_t dvd_plugin_read (input_plugin_t *this_gen, char *buf, off_t nlen) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; int bytes_read; if (nlen != DVD_VIDEO_LB_LEN) { LOG_MSG(this->xine, _("input_dvd: error read: %Ld bytes is not a sector!\n"), nlen); return 0; } if (this->file_size_left < nlen) return 0; bytes_read = read (this->raw_fd, buf, DVD_VIDEO_LB_LEN); if (bytes_read == DVD_VIDEO_LB_LEN) { this->file_lbcur++; this->file_size_left -= DVD_VIDEO_LB_LEN; return DVD_VIDEO_LB_LEN; } else if (bytes_read < 0) { LOG_MSG(this->xine, _("input_dvd: read error in input_dvd plugin (%s)\n"), strerror (errno)); } else { LOG_MSG(this->xine, _("input_dvd: short read in input_dvd (%d != %d)\n"), bytes_read, DVD_VIDEO_LB_LEN); } return 0; } static buf_element_t *dvd_plugin_read_block (input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t nlen) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; buf_element_t *buf; if (nlen != DVD_VIDEO_LB_LEN || this->file_size_left < nlen) { /* * Hide the error reporting now, demuxer try to read 6 bytes * at STAGE_BY_CONTENT probe stage */ if(nlen != DVD_VIDEO_LB_LEN) LOG_MSG(this->xine, _("input_dvd: error in input_dvd plugin read: %Ld bytes " "is not a sector!\n"), nlen); return NULL; } if ((buf = read_cache_read_block (this->read_cache, (off_t)this->file_lbcur*DVD_VIDEO_LB_LEN))) { this->file_lbcur++; this->file_size_left -= DVD_VIDEO_LB_LEN; buf->type = BUF_DEMUX_BLOCK; } else { LOG_MSG(this->xine, _("input_dvd: read error in input_dvd plugin\n")); } return buf; } static off_t dvd_plugin_seek (input_plugin_t *this_gen, off_t offset, int origin) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; offset /= DVD_VIDEO_LB_LEN; switch (origin) { case SEEK_END: offset = (this->file_size / DVD_VIDEO_LB_LEN) - offset; case SEEK_SET: this->file_lbcur = this->file_lbstart + offset; this->file_size_left = this->file_size - (offset * DVD_VIDEO_LB_LEN); break; case SEEK_CUR: if (offset) { this->file_lbcur += offset; this->file_size_left = this->file_size - ((this->file_lbcur - this->file_lbstart) * DVD_VIDEO_LB_LEN); } else { return (this->file_lbcur - this->file_lbstart) * (off_t) DVD_VIDEO_LB_LEN; } break; default: LOG_MSG(this->xine, _("input_dvd: seek: %d is an unknown origin\n"), origin); } return lseek (this->raw_fd, this->file_lbcur * (off_t) DVD_VIDEO_LB_LEN, SEEK_SET) - this->file_lbstart * (off_t) DVD_VIDEO_LB_LEN; } static off_t dvd_plugin_get_current_pos (input_plugin_t *this_gen){ dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; return ((this->file_lbcur - this->file_lbstart) * DVD_VIDEO_LB_LEN); } static off_t dvd_plugin_get_length (input_plugin_t *this_gen) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; return this->file_size; } static uint32_t dvd_plugin_get_blocksize (input_plugin_t *this_gen) { return DVD_VIDEO_LB_LEN; } static int dvd_plugin_eject_media (input_plugin_t *this_gen) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; int ret, status; int fd; if((fd = open(this->device, O_RDONLY|O_NONBLOCK)) > -1) { #if defined (HAVE_LINUX_CDROM_H) if((status = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)) > 0) { switch(status) { case CDS_TRAY_OPEN: if((ret = ioctl(fd, CDROMCLOSETRAY)) != 0) { LOG_MSG(this->xine, _("input_dvd: CDROMCLOSETRAY failed: %s\n"), strerror(errno)); } break; case CDS_DISC_OK: if((ret = ioctl(fd, CDROMEJECT)) != 0) { LOG_MSG(this->xine, _("input_dvd: CDROMEJECT failed: %s\n"), strerror(errno)); } break; } } else { LOG_MSG(this->xine, _("input_dvd: CDROM_DRIVE_STATUS failed: %s\n"), strerror(errno)); close(fd); return 0; } #elif defined (HAVE_CDIO_H) # if defined (__sun) status = 0; if ((ret = ioctl(fd, CDROMEJECT)) != 0) { LOG_MSG(this->xine, _("input_dvd: CDROMEJECT failed: %s\n"), strerror(errno)); } # else if (ioctl(fd, CDIOCALLOW) == -1) { LOG_MSG(this->xine, _("ioctl(cdromallow): %s"), strerror(errno)); } else { if (ioctl(fd, CDIOCEJECT) == -1) { LOG_MSG(this->xine, _("ioctl(cdromeject): %s"), strerror(errno)); } } # endif #endif close(fd); } return 1; } static void dvd_plugin_close (input_plugin_t *this_gen) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; closeDrive (this); } static void dvd_plugin_stop (input_plugin_t *this_gen) { dvd_plugin_close(this_gen); } static char *dvd_plugin_get_description (input_plugin_t *this_gen) { return _("dvd device input plugin as shipped with xine"); } static char *dvd_plugin_get_identifier (input_plugin_t *this_gen) { return "DVD"; } static mrl_t **dvd_plugin_get_dir (input_plugin_t *this_gen, char *filename, int *nEntries) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; int i, fd; *nEntries = 0; if (filename) return NULL; if((fd = open(this->device, O_RDONLY /* | O_NONBLOCK */ )) > -1) { int nFiles, nFiles2; UDFListDir (fd, "/VIDEO_TS", MAX_DIR_ENTRIES, this->filelist, &nFiles); nFiles2 = 0; for (i=0; ifilelist[i]); if (nLen<4) continue; if (!strcasecmp (&this->filelist[i][nLen-4], ".VOB")) { char str[1024]; if(nFiles2 >= this->mrls_allocated_entries) { ++this->mrls_allocated_entries; /* note: 1 extra pointer for terminating NULL */ this->mrls = realloc(this->mrls, (this->mrls_allocated_entries+1) * sizeof(mrl_t*)); this->mrls[nFiles2] = (mrl_t *) xine_xmalloc(sizeof(mrl_t)); } if(this->mrls[nFiles2]->mrl) { this->mrls[nFiles2]->mrl = (char *) realloc(this->mrls[nFiles2]->mrl, strlen(this->filelist[i]) + 7); } else { this->mrls[nFiles2]->mrl = (char *) xine_xmalloc(strlen(this->filelist[i]) + 7); } this->mrls[nFiles2]->origin = NULL; sprintf(this->mrls[nFiles2]->mrl, "dvd://%s", this->filelist[i]); this->mrls[nFiles2]->link = NULL; this->mrls[nFiles2]->type = (0 | mrl_dvd); /* determine size */ memset(&str, 0, sizeof(str)); sprintf (str, "/VIDEO_TS/%s", this->filelist[i]); UDFFindFile(fd, str, &this->mrls[nFiles2]->size); nFiles2++; } } *nEntries = nFiles2; close (fd); } else { LOG_MSG(this->xine, _("input_dvd: unable to open dvd drive (%s): %s\n"), this->device, strerror(errno)); return NULL; } /* * Freeing exceeded mrls if exists. */ while(this->mrls_allocated_entries > *nEntries) { MRL_ZERO(this->mrls[this->mrls_allocated_entries - 1]); free(this->mrls[this->mrls_allocated_entries--]); } /* * This is useful to let UI know where it should stops ;-). */ this->mrls[*nEntries] = NULL; return this->mrls; } static char **dvd_plugin_get_autoplay_list (input_plugin_t *this_gen, int *nFiles) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; int i, fd; if((fd = open(this->device, O_RDONLY /* | O_NONBLOCK */ )) > -1) { int nFiles3, nFiles2; UDFListDir (fd, "/VIDEO_TS", MAX_DIR_ENTRIES, this->filelist, &nFiles3); nFiles2 = 0; for (i=0; ifilelist[i]); if (nLen<4) continue; if (!strcasecmp (&this->filelist[i][nLen-4], ".VOB")) { if(this->filelist2[nFiles2] == NULL) this->filelist2[nFiles2] = (char *) realloc(this->filelist2[nFiles2], sizeof(char *) * 256); sprintf (this->filelist2[nFiles2], "dvd://%s", this->filelist[i]); nFiles2++; } } *nFiles = nFiles2; this->filelist2[*nFiles] = (char *) realloc(this->filelist2[*nFiles], sizeof(char *)); this->filelist2[*nFiles] = NULL; close (fd); } else { LOG_MSG(this->xine, _("input_dvd: unable to open dvd drive (%s): %s\n"), this->device, strerror(errno)); *nFiles = 0; return NULL; } return this->filelist2; } static char* dvd_plugin_get_mrl (input_plugin_t *this_gen) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; return this->mrl; } static int dvd_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type) { /* switch(data_type) { case INPUT_OPTIONAL_DATA_CLUT: ... return INPUT_OPTIONAL_SUCCESS; break; case INPUT_OPTIONAL_DATA_AUDIOLANG: ... return INPUT_OPTIONAL_SUCCESS; break; } */ return INPUT_OPTIONAL_UNSUPPORTED; } static void dvd_plugin_dispose (input_plugin_t *this_gen ) { dvd_input_plugin_t *this = (dvd_input_plugin_t *) this_gen; int i; read_cache_free (this->read_cache); for (i = 0; i < MAX_DIR_ENTRIES; i++) { free (this->filelist[i]); free (this->filelist2[i]); } free (this->mrls); free (this); } input_plugin_t *init_input_plugin (int iface, xine_t *xine) { dvd_input_plugin_t *this; config_values_t *config; int i; if (iface != 7) { LOG_MSG(xine, _("dvd input plugin doesn't support plugin API version %d.\n" "PLUGIN DISABLED.\n" "This means there's a version mismatch between xine and this input" "plugin.\nInstalling current input plugins should help.\n"), iface); return NULL; } this = (dvd_input_plugin_t *) xine_xmalloc (sizeof (dvd_input_plugin_t)); config = xine->config; this->xine = xine; for (i = 0; i < MAX_DIR_ENTRIES; i++) { this->filelist[i] = (char *) xine_xmalloc(sizeof(char *) * 256); this->filelist2[i] = (char *) xine_xmalloc(sizeof(char *) * 256); } this->input_plugin.interface_version = INPUT_PLUGIN_IFACE_VERSION; this->input_plugin.get_capabilities = dvd_plugin_get_capabilities; this->input_plugin.open = dvd_plugin_open; this->input_plugin.read = dvd_plugin_read; this->input_plugin.read_block = dvd_plugin_read_block; this->input_plugin.seek = dvd_plugin_seek; this->input_plugin.get_current_pos = dvd_plugin_get_current_pos; this->input_plugin.get_length = dvd_plugin_get_length; this->input_plugin.get_blocksize = dvd_plugin_get_blocksize; this->input_plugin.eject_media = dvd_plugin_eject_media; this->input_plugin.close = dvd_plugin_close; this->input_plugin.stop = dvd_plugin_stop; this->input_plugin.get_identifier = dvd_plugin_get_identifier; this->input_plugin.get_description = dvd_plugin_get_description; this->input_plugin.get_dir = dvd_plugin_get_dir; this->input_plugin.get_mrl = dvd_plugin_get_mrl; this->input_plugin.get_autoplay_list = dvd_plugin_get_autoplay_list; this->input_plugin.get_optional_data = dvd_plugin_get_optional_data; this->input_plugin.dispose = dvd_plugin_dispose; this->input_plugin.is_branch_possible= NULL; /* disable branch until we fix the problems branching from menu vob to video vob this->input_plugin.is_branch_possible= dvd_plugin_is_branch_possible; */ this->device = config->register_string(config, "input.dvd_device", DVD, "path to your local dvd device file", NULL, device_change_cb, (void *)this); this->raw_device = config->register_string(config, "input.dvd_raw_device", RDVD, "path to a raw device set up for dvd access", NULL, rawdevice_change_cb, (void*)this); #ifdef __sun check_solaris_vold_device(this); #endif this->mrls_allocated_entries = 0; this->mrls = xine_xmalloc(sizeof(mrl_t*)); this->mrl = NULL; this->config = config; this->dvd_fd = -1; this->raw_fd = -1; this->read_cache = read_cache_new (); return (input_plugin_t *) this; }