/* * 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.36 2001/11/18 03:53:23 guenter 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" #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; 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 */ /* ***************************************************************** */ static int openDrive (dvd_input_plugin_t *this) { this->dvd_fd = open(this->device, O_RDONLY /* | O_NONBLOCK */ ); if (this->dvd_fd < 0) { printf ("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(int fd, 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(fd, USCSICMD, &sc)) { perror("USCSICMD dvd_read_copyright"); return -1; } if (sc.uscsi_status) { fprintf(stderr, "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) { printf ("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) { printf ("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) { printf ("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->raw_fd, &dvd) < 0) printf ("input_dvd: Could not read Copyright Structure.\n" " Assuming disk is not encrypted.\n"); else encrypted = (dvd.copyright.cpst != 0); } #endif if( encrypted ) { printf("\ninput_dvd: Sorry, Xine doesn't play encrypted DVDs. The legal status of CSS\n" " decryption is unclear and we will not provide such code.\n\n"); return 0; } snprintf (str, sizeof(str), "/VIDEO_TS/%s", filename); if (!(lbnum = UDFFindFile(this->dvd_fd, str, size))) { printf ("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_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) { printf ("input_dvd: Unable to find >%s< on dvd.\n", filename); return 0; } this->file_size_left = this->file_size; 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) { printf ("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) printf ("input_dvd: read error in input_dvd plugin (%s)\n", strerror (errno)); else printf ("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) printf ("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 { printf ("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: printf ("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) { printf ("input_dvd: CDROMCLOSETRAY failed: %s\n", strerror(errno)); } break; case CDS_DISC_OK: if((ret = ioctl(fd, CDROMEJECT)) != 0) { printf ("input_dvd: CDROMEJECT failed: %s\n", strerror(errno)); } break; } } else { printf ("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) { printf("input_dvd: CDROMEJECT failed: %s\n", strerror(errno)); } # else if (ioctl(fd, CDIOCALLOW) == -1) { perror("ioctl(cdromallow)"); } else { if (ioctl(fd, CDIOCEJECT) == -1) { perror("ioctl(cdromeject)"); } } # 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 { printf ("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")) { sprintf (this->filelist2[nFiles2], "dvd://%s", this->filelist[i]); nFiles2++; } } *nFiles = nFiles2; this->filelist2[*nFiles] = NULL; close (fd); } else { printf ("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; } input_plugin_t *init_input_plugin (int iface, xine_t *xine) { dvd_input_plugin_t *this; config_values_t *config; int i; if (iface != 5) { printf("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; for (i = 0; i < MAX_DIR_ENTRIES; i++) { this->filelist[i] = (char *) xine_xmalloc (256); this->filelist2[i] = (char *) xine_xmalloc (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.is_branch_possible= NULL; this->device = config->register_string(config, "input.dvd_device", DVD, "path to your local dvd device file", NULL, NULL, NULL); this->raw_device = config->register_string(config, "input.dvd_raw_device", RDVD, "path to a raw device set up for dvd access", NULL, NULL, NULL); #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; }