int
main(int argc,char *argv[])
{
  int   toc_bias             =  0;
  int   force_cdrom_endian   = -1;
  int   output_type          =  1; /* 0=raw, 1=wav, 2=aifc */
  int   output_endian        =  0; /* -1=host, 0=little, 1=big */
  int   query_only           =  0;
  int   batch                =  0;
  int   run_cache_test       =  0;
  long int force_cdrom_overlap  = -1;
  long int force_cdrom_sectors  = -1;
  long int force_cdrom_speed    =  0;
  long int force_overread       =  0;
  long int sample_offset        =  0;
  long int test_flags           =  0;
  long int toc_offset           =  0;
  long int max_retries          = 20;

  char *logfile_name=NULL;
  char *reportfile_name=NULL;

  /* full paranoia, but allow skipping */
  int paranoia_mode=PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;

  int out;

  int c,long_option_index;

  atexit(cleanup);

  while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){
    switch(c){
    case 'a':
      output_type=2;
      output_endian=1;
      break;
    case 'B':
      batch=1;
      break;
    case 'c':
      force_cdrom_endian=0;
      break;
    case 'C':
      force_cdrom_endian=1;
      break;
    case 'e':
      callscript=1;
      fprintf(stderr,
              "Sending all callback output to stderr for wrapper script\n");
      break;
    case 'f':
      output_type=3;
      output_endian=1;
      break;
    case 'F':
      paranoia_mode&=~(PARANOIA_MODE_FRAGMENT);
      break;
    case 'g':
    case 'k':
    case 'd':
      if (force_cdrom_device) {
        fprintf(stderr,
                "Multiple cdrom devices given. Previous device %s ignored\n",
                force_cdrom_device);
        free(force_cdrom_device);
      }
      force_cdrom_device=strdup(optarg);
      break;
    case 'h':
      usage(stdout);
      exit(0);
    case 'l':
      if(logfile_name)free(logfile_name);
      logfile_name=NULL;
      if(optarg)
	logfile_name=strdup(optarg);
      logfile_open=1;
      break;
    case 'L':
      if(reportfile_name)free(reportfile_name);
      reportfile_name=NULL;
      if(optarg)
	reportfile_name=strdup(optarg);
      reportfile_open=1;
      break;
    case 'm':
      {
        long int mmc_timeout_sec;
        if (get_int_arg(c, &mmc_timeout_sec)) {
          mmc_timeout_ms = 1000*mmc_timeout_sec;
        }
      }
      break;
    case 'n':
      get_int_arg(c, &force_cdrom_sectors);
      break;
    case 'o':
      get_int_arg(c, &force_cdrom_overlap);
      break;
    case 'O':
      get_int_arg(c, &sample_offset);
      break;
    case 'p':
      output_type=0;
      output_endian=-1;
      break;
    case 'r':
      output_type=0;
      output_endian=0;
      break;
    case 'q':
      verbose=CDDA_MESSAGE_FORGETIT;
      quiet=1;
      break;
    case 'Q':
      query_only=1;
      break;
    case 'R':
      output_type=0;
      output_endian=1;
      break;
    case 'S':
      get_int_arg(c, &force_cdrom_speed);
      break;
    case 't':
      get_int_arg(c, &toc_offset);
      break;
    case 'T':
      toc_bias=-1;
      break;
    case 'v':
      verbose=CDDA_MESSAGE_PRINTIT;
      quiet=0;
      break;
    case 'V':
      fprintf(stderr,PARANOIA_VERSION);
      fprintf(stderr,"\n");
      exit(0);
      break;
    case 'w':
      output_type=1;
      output_endian=0;
      break;
    case 'W':
      paranoia_mode&=~PARANOIA_MODE_REPAIR;
      break;
    case 'x':
      get_int_arg(c, &test_flags);
      break;
    case 'X':
      /*paranoia_mode&=~(PARANOIA_MODE_SCRATCH|PARANOIA_MODE_REPAIR);*/
      abort_on_skip=1;
      break;
    case 'Y':
      paranoia_mode|=PARANOIA_MODE_OVERLAP; /* cdda2wav style overlap
                                                check only */
      paranoia_mode&=~PARANOIA_MODE_VERIFY;
      break;
    case 'Z':
      paranoia_mode=PARANOIA_MODE_DISABLE;
      break;
    case 'A':
      run_cache_test=1;
      query_only=1;
      reportfile_open=1;
      verbose=CDDA_MESSAGE_PRINTIT;
      break;
    case 'z':
      if (optarg) {
        get_int_arg(c, &max_retries);
        paranoia_mode&=~PARANOIA_MODE_NEVERSKIP;
      } else {
        paranoia_mode|=PARANOIA_MODE_NEVERSKIP;
      }
      break;
    case 'E':
      force_overread=1;
      break;
    default:
      usage(stderr);
      exit(1);
    }
  }

  if(logfile_open){
    if(logfile_name==NULL)
      logfile_name=strdup("cdparanoia.log");
    if(!strcmp(logfile_name,"-")){
      logfile=stdout;
      logfile_open=0;
    }else{
      logfile=fopen(logfile_name,"w");
      if(logfile==NULL){
	report("Cannot open log summary file %s: %s",logfile_name,
	       strerror(errno));
	exit(1);
      }
    }
  }
  if(reportfile_open){
    if(reportfile_name==NULL)
      reportfile_name=strdup("cdparanoia.log");
    if(!strcmp(reportfile_name,"-")){
      reportfile=stdout;
      reportfile_open=0;
    }else{
      if(logfile_name && !strcmp(reportfile_name,logfile_name)){
	reportfile=logfile;
	reportfile_open=0;
      }else{
	reportfile=fopen(reportfile_name,"w");
	if(reportfile==NULL){
	  report("Cannot open debug log file %s: %s",reportfile_name,
		 strerror(errno));
	  exit(1);
	}
      }
    }
  }

  if(logfile){
    /* log command line and version */
    int i;
    for (i = 0; i < argc; i++)
      fprintf(logfile,"%s ",argv[i]);
    fprintf(logfile,"\n");

    if(reportfile!=logfile){
      fprintf(logfile,VERSION);
      fprintf(logfile,"\n");
      fprintf(logfile,"Using cdda library version: %s\n",cdda_version());
      fprintf(logfile,"Using paranoia library version: %s\n",paranoia_version());
    }
    fflush(logfile);
  }

  if(reportfile && reportfile!=logfile){
    /* log command line */
    int i;
    for (i = 0; i < argc; i++)
      fprintf(reportfile,"%s ",argv[i]);
    fprintf(reportfile,"\n");
    fflush(reportfile);
  }

  if(optind>=argc && !query_only){
    if(batch)
      span=NULL;
    else{
      /* D'oh.  No span. Fetch me a brain, Igor. */
      usage(stderr);
      exit(1);
    }
  }else
    if (argv[optind]) span=strdup(argv[optind]);

  report(PARANOIA_VERSION);
  if(verbose){
    report("Using cdda library version: %s",cdda_version());
    report("Using paranoia library version: %s",paranoia_version());
  }

  /* Query the cdrom/disc; we may need to override some settings */

  if(force_cdrom_device)
    d=cdda_identify(force_cdrom_device,verbose,NULL);
  else {
    driver_id_t driver_id;
    char **ppsz_cd_drives = cdio_get_devices_with_cap_ret(NULL,
                                                          CDIO_FS_AUDIO,
                                                          false,
                                                          &driver_id);
    if (ppsz_cd_drives && *ppsz_cd_drives) {
      d=cdda_identify(*ppsz_cd_drives,verbose, NULL);
    } else {
      report("\nUnable find or access a CD-ROM drive with an audio CD"
             " in it.");
      report("\nYou might try specifying the drive, especially if it has"
             " mixed-mode (and non-audio) format tracks");
      exit(1);
    }

    cdio_free_device_list(ppsz_cd_drives);
  }

  if(!d){
    if(!verbose)
      report("\nUnable to open cdrom drive; -v might give more information.");
    exit(1);
  }

  if(verbose)
    cdda_verbose_set(d,CDDA_MESSAGE_PRINTIT,CDDA_MESSAGE_PRINTIT);
  else
    cdda_verbose_set(d,CDDA_MESSAGE_PRINTIT,CDDA_MESSAGE_FORGETIT);

  /* possibly force hand on endianness of drive, sector request size */
  if(force_cdrom_endian!=-1){
    d->bigendianp=force_cdrom_endian;
    switch(force_cdrom_endian){
    case 0:
      report("Forcing CDROM sense to little-endian; ignoring preset and autosense");
      break;
    case 1:
      report("Forcing CDROM sense to big-endian; ignoring preset and autosense");
      break;
    }
  }
  if (force_cdrom_sectors!=-1) {
    if(force_cdrom_sectors<0 || force_cdrom_sectors>100){
      report("Default sector read size must be 1<= n <= 100\n");
      cdda_close(d);
      d=NULL;
      exit(1);
    }
    report("Forcing default to read %ld sectors; "
	   "ignoring preset and autosense",force_cdrom_sectors);
    d->nsectors=force_cdrom_sectors;
  }
  if (force_cdrom_overlap!=-1) {
    if (force_cdrom_overlap<0 || force_cdrom_overlap>CDIO_CD_FRAMES_PER_SEC) {
      report("Search overlap sectors must be 0<= n <=75\n");
      cdda_close(d);
      d=NULL;
      if(logfile && logfile != stdout)
        fclose(logfile);
      exit(1);
    }
    report("Forcing search overlap to %ld sectors; "
	   "ignoring autosense",force_cdrom_overlap);
  }

  switch( cdda_open(d) ) {
  case -2:case -3:case -4:case -5:
    report("\nUnable to open disc.  Is there an audio CD in the drive?");
    exit(1);
  case -6:
    report("\nCdparanoia could not find a way to read audio from this drive.");
    exit(1);
  case 0:
    break;
  default:
    report("\nUnable to open disc.");
    exit(1);
  }

  d->i_test_flags = test_flags;

  if (force_cdrom_speed == 0) force_cdrom_speed = -1;

  if (force_cdrom_speed != -1) {
    report("\nAttempting to set speed to %ldx... ", force_cdrom_speed);
  } else {
    if (verbose)
      report("\nAttempting to set cdrom to full speed... ");
  }

  if (cdda_speed_set(d, force_cdrom_speed)) {
    if (verbose || force_cdrom_speed != -1)
      report("\tCDROM speed set FAILED. Continuing anyway...");
  } else {
    if (verbose)
      report("\tdrive returned OK.");
  }

  if(run_cache_test){
    int warn=analyze_cache(d, stderr, reportfile, force_cdrom_speed);

    if(warn==0){
      reportC("\nDrive tests OK with Paranoia.\n\n");
      return 0;
    }

    if(warn==1)
      reportC("\nWARNING! PARANOIA MAY NOT BE TRUSTWORTHY WITH THIS DRIVE!\n"
	      "\nThe Paranoia library may not model this CDROM drive's cache"
	      "\ncorrectly according to this analysis run. Analysis is not"
	      "\nalways accurate (it can be fooled by machine load or random"
	      "\nkernel latencies), but if a failed result happens more often"
	      "\nthan one time in twenty on an unloaded machine, please mail"
	      "\nthe %s file produced by this failed analysis to"
	      "\[email protected] to assist developers in extending"
	      "\nParanoia to handle this CDROM properly.\n\n",reportfile_name);
    return 1;
  }


  /* Dump the TOC */
  if (query_only || verbose ) display_toc(d);

  if (query_only) exit(0);

  /* bias the disc.  A hack.  Of course. this is never the default. */
  /*
     Some CD-ROM/CD-R drives will add an offset to the position on
     reading audio data. This is usually around 500-700 audio samples
     (ca. 1/75 second) on reading. So when this program queries a
     specific sector, it might not receive exactly that sector, but
     shifted by some amount.

     Note that if ripping includes the end of the CD, this will this
     cause this program to attempt to read partial sectors before or
     past the known user data area of the disc, probably causing read
     errors on most drives and possibly even hard lockups on some
     buggy hardware.

     [Note to libcdio driver hackers: make sure all CD-drivers don't
     try to read outside of the stated disc boundaries.]
  */
  if(sample_offset){
    toc_offset+=sample_offset/588;
    sample_offset%=588;
    if(sample_offset<0){
      sample_offset+=588;
      toc_offset--;
    }
  }

  if (toc_bias) {
    toc_offset = -cdda_track_firstsector(d,1);
  }

  {
    int i;
    for( i=0; i < d->tracks+1; i++ )
      d->disc_toc[i].dwStartSector+=toc_offset;
  }

  if (d->nsectors==1) {
    report("WARNING: The autosensed/selected sectors per read value is\n"
           "         one sector, making it very unlikely Paranoia can \n"
           "         work.\n\n"
           "         Attempting to continue...\n\n");
  }

  /* parse the span, set up begin and end sectors */

  {
    long i_first_lsn;
    long i_last_lsn;
    long batch_first;
    long batch_last;
    int batch_track;

    if (span) {
      /* look for the hyphen */
      char *span2=strchr(span,'-');
      if(strrchr(span,'-')!=span2){
        report("Error parsing span argument");
        exit(1);
      }

      if (span2!=NULL) {
        *span2='\0';
        span2++;
      }

      i_first_lsn=parse_offset(d, span, -1);

      if(i_first_lsn==-1)
        i_last_lsn=parse_offset(d, span2, cdda_disc_firstsector(d));

      else
        i_last_lsn=parse_offset(d, span2, i_first_lsn);

      if (i_first_lsn == -1) {
        if (i_last_lsn == -1) {
          report("Error parsing span argument");
          exit(1);
        } else {
          i_first_lsn=cdda_disc_firstsector(d);
        }
      } else {
        if (i_last_lsn==-1) {
          if (span2) { /* There was a hyphen */
            i_last_lsn=cdda_disc_lastsector(d);
          } else {
            i_last_lsn=
              cdda_track_lastsector(d,cdda_sector_gettrack(d, i_first_lsn));
          }
        }
      }
    } else {
      i_first_lsn = cdda_disc_firstsector(d);
      i_last_lsn  = cdda_disc_lastsector(d);
    }

    {
      int track1 = cdda_sector_gettrack(d, i_first_lsn);

      int track2 = cdda_sector_gettrack(d, i_last_lsn);
      long off1  = i_first_lsn - cdda_track_firstsector(d, track1);
      long off2  = i_last_lsn  - cdda_track_firstsector(d, track2);
      int i;

      for( i=track1; i<=track2; i++ ) {
        if(i != 0 && !cdda_track_audiop(d,i)){
	  report("Selected span contains non audio track at track %02d.  Aborting.\n\n", i);
          exit(1);
          if (i == 0)
            i = cdio_get_first_track_num(d->p_cdio) - 1;
        }
      }

      report("Ripping from sector %7ld (track %2d [%d:%02d.%02d])\n"
	     "\t  to sector %7ld (track %2d [%d:%02d.%02d])\n",
	     i_first_lsn,
	     track1,
	     (int) (off1/(CDIO_CD_FRAMES_PER_MIN)),
	     (int) ((off1/CDIO_CD_FRAMES_PER_SEC) % CDIO_CD_SECS_PER_MIN),
	     (int)(off1 % CDIO_CD_FRAMES_PER_SEC),
	     i_last_lsn,
	     track2,
	     (int) (off2/(CDIO_CD_FRAMES_PER_MIN)),
	     (int) ((off2/CDIO_CD_FRAMES_PER_SEC) % CDIO_CD_SECS_PER_MIN),
	     (int)(off2 % CDIO_CD_FRAMES_PER_SEC));

    }

    if (toc_offset && !force_overread) {
	d->disc_toc[d->tracks].dwStartSector -= toc_offset;
	if (i_last_lsn > cdda_track_lastsector(d, d->tracks))
		i_last_lsn -= toc_offset;
    }
    {
      long cursor;
      int16_t offset_buffer[1176];
      int offset_buffer_used=0;
      int offset_skip=sample_offset*4;
      off_t sectorlen;

#if defined(HAVE_GETUID) && (defined(HAVE_SETEUID) || defined(HAVE_SETEGID))
      int dummy __attribute__((unused));
#endif
      p=paranoia_init(d);
      paranoia_modeset(p,paranoia_mode);
      if(force_cdrom_overlap!=-1)paranoia_overlapset(p,force_cdrom_overlap);

      if(verbose) {
        cdda_verbose_set(d,CDDA_MESSAGE_LOGIT,CDDA_MESSAGE_LOGIT);
        cdio_loglevel_default = CDIO_LOG_INFO;
      } else
        cdda_verbose_set(d,CDDA_MESSAGE_FORGETIT,CDDA_MESSAGE_FORGETIT);

      paranoia_seek(p,cursor=i_first_lsn,SEEK_SET);

      /* this is probably a good idea in general */
#if defined(HAVE_GETUID) && defined(HAVE_SETEUID)
      dummy = seteuid(getuid());
#endif
#if defined(HAVE_GETGID) && defined(HAVE_SETEGID)
      dummy = setegid(getgid());
#endif

      /* we'll need to be able to read one sector past user data if we
         have a sample offset in order to pick up the last bytes.  We
         need to set the disc length forward here so that the libs are
         willing to read past, assuming that works on the hardware, of
         course */
      if(sample_offset && force_overread)
        d->disc_toc[d->tracks].dwStartSector++;

      while(cursor<=i_last_lsn){
        char outfile_name[PATH_MAX];
        if ( batch ){
          batch_first = cursor;
          batch_track = cdda_sector_gettrack(d,cursor);
          batch_last  = cdda_track_lastsector(d, batch_track);
          if (batch_last>i_last_lsn) batch_last=i_last_lsn;
        } else {
          batch_first = i_first_lsn;
          batch_last  = i_last_lsn;
          batch_track = -1;
        }

        callbegin=batch_first;
        callend=batch_last;

        /* argv[optind] is the span, argv[optind+1] (if exists) is outfile */

        if (optind+1<argc) {
          if (!strcmp(argv[optind+1],"-") ){
            out = dup(fileno(stdout));
            if(out==-1){
              report("Cannot dupplicate stdout: %s",
                     strerror(errno));
              exit(1);
            }
            if(batch)
              report("Are you sure you wanted 'batch' "
                     "(-B) output with stdout?");
            report("outputting to stdout\n");
            if(logfile){
              fprintf(logfile,"outputting to stdout\n");
              fflush(logfile);
            }
            outfile_name[0]='\0';
          } else {
            char dirname[PATH_MAX];
            char *basename=split_base_dir(argv[optind+1], dirname,
					  PATH_MAX);

	    if (NULL == basename) {
	      report("Output filename too long");
	      exit(1);
	    }

            if(batch) {
	      if (strlen(argv[optind+1]) - 10 > PATH_MAX) {
		report("Output filename too long");
		exit(1);
	      }
              snprintf(outfile_name, PATH_MAX,
		       " %strack%02d.%s", dirname,
                       batch_track, basename);
            } else
              snprintf(outfile_name, PATH_MAX, "%s%s", dirname, basename);

            if(basename[0]=='\0'){
              switch (output_type) {
              case 0: /* raw */
                strncat(outfile_name, "cdda.raw", sizeof("cdda.raw"));
                break;
              case 1:
                strncat(outfile_name, "cdda.wav", sizeof("cdda.wav"));
                break;
              case 2:
                strncat(outfile_name, "cdda.aifc", sizeof("cdda.aifc"));
                break;
              case 3:
                strncat(outfile_name, "cdda.aiff", sizeof("cdda.aiff"));
                break;
              }
            }

            out=open(outfile_name,O_RDWR|O_CREAT|O_TRUNC|O_BINARY,0666);
            if(out==-1){
              report("Cannot open specified output file %s: %s",
                      outfile_name, strerror(errno));
              exit(1);
            }
            report("outputting to %s\n", outfile_name);
            if(logfile){
              fprintf(logfile,"outputting to %s\n",outfile_name);
              fflush(logfile);
            }
          }
        } else {
          /* default */
          if (batch)
            sprintf(outfile_name,"track%02d.", batch_track);
          else
            outfile_name[0]='\0';

          switch(output_type){
          case 0: /* raw */
            strncat(outfile_name, "cdda.raw", sizeof("cdda.raw"));
            break;
          case 1:
            strncat(outfile_name, "cdda.wav", sizeof("cdda.wav"));
            break;
          case 2:
            strncat(outfile_name, "cdda.aifc", sizeof("cdda.aifc"));
            break;
          case 3:
            strncat(outfile_name, "cdda.aiff", sizeof("cdda.aiff"));
            break;
          }

          out = open(outfile_name, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0666);
          if(out==-1){
            report("Cannot open default output file %s: %s", outfile_name,
                    strerror(errno));
            exit(1);
          }
          report("outputting to %s\n", outfile_name);
          if(logfile){
            fprintf(logfile,"outputting to %s\n",outfile_name);
            fflush(logfile);
          }

        }

	sectorlen = batch_last - batch_first + 1;
	if (cdda_sector_gettrack(d, cursor) == d->tracks &&
		toc_offset > 0 && !force_overread){
		sectorlen += toc_offset;
	}
        switch(output_type) {
        case 0: /* raw */
          break;
        case 1: /* wav */
	  WriteWav(out, sectorlen * CD_FRAMESIZE_RAW);
          break;
        case 2: /* aifc */
	  WriteAifc(out, sectorlen * CD_FRAMESIZE_RAW);
          break;
        case 3: /* aiff */
	  WriteAiff(out, sectorlen * CD_FRAMESIZE_RAW);
          break;
        }

        /* Off we go! */

        if(offset_buffer_used){
          /* partial sector from previous batch read */
          cursor++;
          if (buffering_write(out,
                              ((char *)offset_buffer)+offset_buffer_used,
                              CDIO_CD_FRAMESIZE_RAW-offset_buffer_used)){
            report("Error writing output: %s", strerror(errno));
            exit(1);
          }
        }

        skipped_flag=0;
        while(cursor<=batch_last){
          /* read a sector */
          int16_t *readbuf=paranoia_read_limited(p, callback, max_retries);
          char *err=cdda_errors(d);
          char *mes=cdda_messages(d);

          if(mes || err)
            fprintf(stderr,"\r                               "
                    "                                           \r%s%s\n",
                    mes?mes:"",err?err:"");

          if (err) free(err);
          if (mes) free(mes);
          if( readbuf==NULL) {
	    if(errno==EBADF || errno==ENOMEDIUM){
	      report("\nparanoia_read: CDROM drive unavailable, bailing.\n");
	      exit(1);
	    }
            skipped_flag=1;
            report("\nparanoia_read: Unrecoverable error, bailing.\n");
            break;
          }
          if(skipped_flag && abort_on_skip){
            cursor=batch_last+1;
            break;
          }

          skipped_flag=0;
          cursor++;

          if (output_endian!=bigendianp()) {
            int i;
            for (i=0; i<CDIO_CD_FRAMESIZE_RAW/2; i++)
              readbuf[i]=UINT16_SWAP_LE_BE_C(readbuf[i]);
          }

          callback(cursor*(CD_FRAMEWORDS)-1, PARANOIA_CB_WROTE);

          if (buffering_write(out,((char *)readbuf)+offset_skip,
                             CDIO_CD_FRAMESIZE_RAW-offset_skip)){
            report("Error writing output: %s", strerror(errno));
            exit(1);
          }
          offset_skip=0;

          if (output_endian != bigendianp()){
            int i;
            for (i=0; i<CDIO_CD_FRAMESIZE_RAW/2; i++)
              readbuf[i] = UINT16_SWAP_LE_BE_C(readbuf[i]);
          }

          /* One last bit of silliness to deal with sample offsets */
          if(sample_offset && cursor>batch_last){
	    if (cdda_sector_gettrack(d, batch_last) < d->tracks || force_overread) {
	      int i;

	      /* Need to flush the buffer when overreading into the leadout */
	      if (cdda_sector_gettrack(d, batch_last) == d->tracks)
		paranoia_seek(p, cursor, SEEK_SET);

	      /* read a sector and output the partial offset.  Save the
		 rest for the next batch iteration */
	      readbuf=paranoia_read_limited(p,callback,max_retries);
	      err=cdda_errors(d);mes=cdda_messages(d);

	      if(mes || err)
		fprintf(stderr,"\r                               "
			"                                           \r%s%s\n",
			mes?mes:"",err?err:"");

	      if(err)free(err);if(mes)free(mes);
	      if(readbuf==NULL){
		skipped_flag=1;
		report("\nparanoia_read: Unrecoverable error reading through "
		       "sample_offset shift\n\tat end of track, bailing.\n");
		break;
	      }
	      if (skipped_flag && abort_on_skip) break;
	      skipped_flag=0;
	      /* do not move the cursor */

	      if(output_endian!=bigendianp())
		for(i=0;i<CD_FRAMESIZE_RAW/2;i++)
		  offset_buffer[i]=UINT16_SWAP_LE_BE_C(readbuf[i]);
	      else
		memcpy(offset_buffer,readbuf,CD_FRAMESIZE_RAW);
	      offset_buffer_used=sample_offset*4;
	      callback(cursor* (CD_FRAMEWORDS), PARANOIA_CB_WROTE);
	    } else {
	      memset(offset_buffer, 0, sizeof(offset_buffer));
	      offset_buffer_used = sample_offset * 4;
	    }

	    if(buffering_write(out,(char *)offset_buffer,
			       offset_buffer_used)){
	      report("Error writing output: %s", strerror(errno));
	      exit(1);
	    }
	  }
        }

	/* Write sectors of silent audio to compensate for
	   missing samples that would be in the leadout */
	if (cdda_sector_gettrack(d, batch_last) == d->tracks &&
		toc_offset > 0 && !force_overread)
	{
		char *silence;
		size_t missing_sector_bytes = CD_FRAMESIZE_RAW * toc_offset;

		silence = calloc(toc_offset, CD_FRAMESIZE_RAW);
		if (!silence || buffering_write(out, silence, missing_sector_bytes)) {
		      report("Error writing output: %s", strerror(errno));
		      exit(1);
		}
		free(silence);
	}

        callback(cursor* (CDIO_CD_FRAMESIZE_RAW/2)-1,
		 PARANOIA_CB_FINISHED);
        buffering_close(out);
        if(skipped_flag){
          /* remove the file */
          report("\nRemoving aborted file: %s", outfile_name);
          unlink(outfile_name);
          /* make the cursor correct if we have another track */
          if(batch_track!=-1){
            batch_track++;
            cursor=cdda_track_firstsector(d,batch_track);
            paranoia_seek(p,cursor, SEEK_SET);
            offset_skip=sample_offset*4;
            offset_buffer_used=0;
          }
        }
        report("\n");
      }

      paranoia_free(p);
      p=NULL;
    }
  }

  report("Done.\n\n");

  return 0;
}
Exemplo n.º 2
0
static int open_cdda(stream_t *st,int m, void* opts, int* file_format) {
  struct cdda_params* p = (struct cdda_params*)opts;
  int mode = p->paranoia_mode;
  int offset = p->toc_offset;
#ifndef HAVE_LIBCDIO
  cdrom_drive* cdd = NULL;
#else
  cdrom_drive_t* cdd = NULL;
#endif
  cdda_priv* priv;
  cd_info_t *cd_info,*cddb_info = NULL;
  unsigned int audiolen=0;
  int last_track;
  int i;
  char *xmcd_file = NULL;

  if(m != STREAM_READ) {
    m_struct_free(&stream_opts,opts);
    return STREAM_UNSUPORTED;
  }

  if(!p->device) {
    if (cdrom_device)
      p->device = strdup(cdrom_device);
    else
      p->device = strdup(DEFAULT_CDROM_DEVICE);
  }

#ifdef HAVE_CDDB
  // cdd_identify returns -1 if it cannot read the TOC,
  // in which case there is no point in calling cddb_resolve
  if(cdd_identify(p->device) >= 0 && strncmp(st->url,"cddb",4) == 0) {
    i = cddb_resolve(p->device, &xmcd_file);
    if(i == 0) {
      cddb_info = cddb_parse_xmcd(xmcd_file);
      free(xmcd_file);
    }
  }
#endif
  
#ifndef HAVE_LIBCDIO
  if(p->generic_dev)
    cdd = cdda_identify_scsi(p->generic_dev,p->device,0,NULL);
  else
#endif
#if defined(__NetBSD__)
    cdd = cdda_identify_scsi(p->device,p->device,0,NULL);
#else
    cdd = cdda_identify(p->device,0,NULL);
#endif

  if(!cdd) {
    mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_MPDEMUX_CDDA_CantOpenCDDADevice);
    m_struct_free(&stream_opts,opts);
    free(cddb_info);
    return STREAM_ERROR;
  }

  cdda_verbose_set(cdd, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);

  if(p->sector_size) {
    cdd->nsectors = p->sector_size;
#ifndef HAVE_LIBCDIO
    cdd->bigbuff = p->sector_size * CD_FRAMESIZE_RAW;
#endif
  }

  if(cdda_open(cdd) != 0) {
    mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_MPDEMUX_CDDA_CantOpenDisc);
    cdda_close(cdd);
    m_struct_free(&stream_opts,opts);
    free(cddb_info);
    return STREAM_ERROR;
  }

  cd_info = cd_info_new();
  mp_msg(MSGT_OPEN,MSGL_INFO,MSGTR_MPDEMUX_CDDA_AudioCDFoundWithNTracks,cdda_tracks(cdd));
  for(i=0;i<cdd->tracks;i++) {
	  char track_name[80];
	  long sec=cdda_track_firstsector(cdd,i+1);
	  long off=cdda_track_lastsector(cdd,i+1)-sec+1;

	  sprintf(track_name, "Track %d", i+1);
	  cd_info_add_track(cd_info, track_name, i+1, (unsigned int)(off/(60*75)), (unsigned int)((off/75)%60), (unsigned int)(off%75), sec, off );
	  audiolen += off;
  }
  cd_info->min  = (unsigned int)(audiolen/(60*75));
  cd_info->sec  = (unsigned int)((audiolen/75)%60);
  cd_info->msec = (unsigned int)(audiolen%75);

  priv = malloc(sizeof(cdda_priv));
  memset(priv, 0, sizeof(cdda_priv));
  priv->cd = cdd;
  priv->cd_info = cd_info;

  if(p->toc_bias)
    offset -= cdda_track_firstsector(cdd,1);

  if(offset) {
    int i;
    for(i = 0 ; i < cdd->tracks + 1 ; i++)
      cdd->disc_toc[i].dwStartSector += offset;
  }

  if(p->speed)
    cdda_speed_set(cdd,p->speed);

  last_track = cdda_tracks(cdd);
  if (p->span.start > last_track) p->span.start = last_track;
  if (p->span.end < p->span.start) p->span.end = p->span.start;
  if (p->span.end > last_track) p->span.end = last_track;
  if(p->span.start)
    priv->start_sector = cdda_track_firstsector(cdd,p->span.start);
  else
    priv->start_sector = cdda_disc_firstsector(cdd);

  if(p->span.end) {
    priv->end_sector = cdda_track_lastsector(cdd,p->span.end);
  } else
    priv->end_sector = cdda_disc_lastsector(cdd);

  priv->cdp = paranoia_init(cdd);
  if(priv->cdp == NULL) {
    cdda_close(cdd);
    free(priv);
    cd_info_free(cd_info);
    m_struct_free(&stream_opts,opts);
    free(cddb_info);
    return STREAM_ERROR;
  }

  if(mode == 0)
    mode = PARANOIA_MODE_DISABLE;
  else if(mode == 1)
    mode = PARANOIA_MODE_OVERLAP;
  else
    mode = PARANOIA_MODE_FULL;
  
  if(p->no_skip)
    mode |= PARANOIA_MODE_NEVERSKIP;
#ifndef HAVE_LIBCDIO
  paranoia_modeset(cdd, mode);

  if(p->search_overlap >= 0)
    paranoia_overlapset(cdd,p->search_overlap);
#else
  paranoia_modeset(priv->cdp, mode);

  if(p->search_overlap >= 0)
    paranoia_overlapset(priv->cdp,p->search_overlap);
#endif

  paranoia_seek(priv->cdp,priv->start_sector,SEEK_SET);
  priv->sector = priv->start_sector;

#ifdef HAVE_CDDB
  if(cddb_info) {
    cd_info_free(cd_info);
    priv->cd_info = cddb_info;
    cd_info_debug( cddb_info );
  }
#endif

  st->priv = priv;
  st->start_pos = priv->start_sector*CD_FRAMESIZE_RAW;
  st->end_pos = priv->end_sector*CD_FRAMESIZE_RAW;
  st->type = STREAMTYPE_CDDA;
  st->sector_size = CD_FRAMESIZE_RAW;

  st->fill_buffer = fill_buffer;
  st->seek = seek;
  st->close = close_cdda;

  *file_format = DEMUXER_TYPE_RAWAUDIO;

  m_struct_free(&stream_opts,opts);

  return STREAM_OK;
}
static long
parse_offset(cdrom_drive_t *d, char *offset, int begin)
{
  track_t i_track= CDIO_INVALID_TRACK;
  long hours   = -1;
  long minutes = -1;
  long seconds = -1;
  long sectors = -1;
  char *time   = NULL;
  char *temp   = NULL;
  long ret;

  if (!offset) return -1;

  /* separate track from time offset */
  temp=strchr(offset,']');
  if(temp){
    *temp='\0';
    temp=strchr(offset,'[');
    if(temp==NULL){
      report("Error parsing span argument");
      exit(1);
    }
    *temp='\0';
    time=temp+1;
  }

  /* parse track */
  {
    int chars=strspn(offset,"0123456789");
    if(chars>0){
      offset[chars]='\0';
      i_track=atoi(offset);
      if (i_track >= cdio_get_first_track_num(d->p_cdio) + d->tracks) {
        /*take track i_first_track-1 as pre-gap of 1st track*/
	report("Track #%d does not exist.",i_track);
        exit(1);
      }
    }
  }

  while(time){
    long val,chars;
    char *sec=strrchr(time,'.');
    if(!sec)sec=strrchr(time,':');
    if(!sec)sec=time-1;

    chars=strspn(sec+1,"0123456789");
    if(chars)
      val=atoi(sec+1);
    else
      val=0;

    switch(*sec){
    case '.':
      if(sectors!=-1){
        report("Error parsing span argument");
        exit(1);
      }
      sectors=val;
      break;
    default:
      if(seconds==-1)
        seconds=val;
      else
        if(minutes==-1)
          minutes=val;
        else
          if(hours==-1)
            hours=val;
          else{
            report("Error parsing span argument");
            exit(1);
          }
      break;
    }

    if (sec<=time) break;
    *sec='\0';
  }

  if (i_track == CDIO_INVALID_TRACK) {
    if (seconds==-1 && sectors==-1) return -1;
    if (begin==-1) {
      ret=cdda_disc_firstsector(d);
    } else
      ret = begin;
  } else {
    if ( seconds==-1 && sectors==-1 ) {
      if (begin==-1){
        /* first half of a span */
        return(cdda_track_firstsector(d, i_track));
      }else{
        return(cdda_track_lastsector(d, i_track));
      }
    } else {
      /* relative offset into a track */
      ret=cdda_track_firstsector(d, i_track);
    }
  }

  /* OK, we had some sort of offset into a track */

  if (sectors != -1) ret += sectors;
  if (seconds != -1) ret += seconds*CDIO_CD_FRAMES_PER_SEC;
  if (minutes != -1) ret += minutes*CDIO_CD_FRAMES_PER_MIN;
  if (hours   != -1) ret += hours  *60*CDIO_CD_FRAMES_PER_MIN;

  /* We don't want to outside of the track; if it's relative, that's OK... */
  if( i_track != CDIO_INVALID_TRACK ){
    if (cdda_sector_gettrack(d,ret) != i_track) {
      report("Time/sector offset goes beyond end of specified track.");
      exit(1);
    }
  }

  /* Don't pass up end of session */

  if( ret>cdda_disc_lastsector(d) ) {
    report("Time/sector offset goes beyond end of disc.");
    exit(1);
  }

  return(ret);
}
Exemplo n.º 4
0
static gboolean
gst_cd_paranoia_src_open (GstCddaBaseSrc * cddabasesrc, const gchar * device)
{
  GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (cddabasesrc);
  gint i;

  GST_DEBUG_OBJECT (src, "trying to open device %s (generic-device=%s) ...",
      device, GST_STR_NULL (src->generic_device));

  /* find the device */
  if (src->generic_device != NULL) {
    src->d = cdda_identify_scsi (src->generic_device, device, FALSE, NULL);
  } else {
    if (device != NULL) {
      src->d = cdda_identify (device, FALSE, NULL);
    } else {
      src->d = cdda_identify ("/dev/cdrom", FALSE, NULL);
    }
  }

  /* fail if the device couldn't be found */
  if (src->d == NULL)
    goto no_device;

  /* set verbosity mode */
  cdda_verbose_set (src->d, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);

  /* open the disc */
  if (cdda_open (src->d))
    goto open_failed;

  if (src->read_speed != -1) {
    cdda_speed_set (src->d, src->read_speed);
  }

  for (i = 1; i < src->d->tracks + 1; i++) {
    GstCddaBaseSrcTrack track = { 0, };

    track.num = i;
    track.is_audio = IS_AUDIO (src->d, i - 1);
    track.start = cdda_track_firstsector (src->d, i);
    track.end = cdda_track_lastsector (src->d, i);
    track.tags = NULL;

    gst_cdda_base_src_add_track (GST_CDDA_BASE_SRC (src), &track);
  }

  /* create the paranoia struct and set it up */
  src->p = paranoia_init (src->d);
  if (src->p == NULL)
    goto init_failed;

  paranoia_modeset (src->p, src->paranoia_mode);

  if (src->search_overlap != -1)
    paranoia_overlapset (src->p, src->search_overlap);

  src->next_sector = -1;

  return TRUE;

  /* ERRORS */
no_device:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
        (_("Could not open CD device for reading.")), ("cdda_identify failed"));
    return FALSE;
  }
open_failed:
  {
    GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
        (_("Could not open CD device for reading.")), ("cdda_open failed"));
    cdda_close (src->d);
    src->d = NULL;
    return FALSE;
  }
init_failed:
  {
    GST_ELEMENT_ERROR (src, LIBRARY, INIT,
        ("failed to initialize paranoia"), ("failed to initialize paranoia"));
    return FALSE;
  }
}
Exemplo n.º 5
0
unsigned LocalAudioCD::Create(const std::string& device, AudioCDPtr *pcd)
{
    *pcd = AudioCDPtr(NULL);

    TRACE << "Calling identify\n";

    cdrom_drive *cdt =  cdda_identify(device.c_str(), CDDA_MESSAGE_FORGETIT, 
				      NULL);

    if (!cdt)
    {
	TRACE << "Can't identify CD drive\n";
	return EINVAL;
    }

    TRACE << "Identified\n";

    int rc = cdda_open(cdt);
    if (rc<0)
    {
	TRACE << "Can't open CD drive\n";
	return (unsigned)errno;
    }

    TRACE << "Opened\n";

    unsigned int total = (unsigned int)cdda_tracks(cdt);

    TRACE << "Got tracks\n";

    if (total == (track_t)-1)
    {
	cdda_close(cdt);
	TRACE << "No audio tracks\n";
	return ENOENT;
    }

    toc_t toc;

    unsigned total_sectors = 0;

    for (track_t i=1; i<=total; ++i) // Peculiar 1-based numbering
    {
	if (cdda_track_audiop(cdt, i))
	{
	    TocEntry te;
	    te.first_sector = (int)cdda_track_firstsector(cdt, i);
	    te.last_sector  = (int)cdda_track_lastsector(cdt, i);
	    TRACE << "Track " << i << " " << te.first_sector
		  << ".." << te.last_sector << "\n";
	    
	    toc.push_back(te);

	    total_sectors += (unsigned)(te.last_sector - te.first_sector + 1);
	}
	else
	{
//	    TRACE << "Track " << i+1 << " not audio\n";
	}
    }

    if (toc.empty())
    {
	cdda_close(cdt);
	TRACE << "No audio tracks\n";
	return ENOENT;
    }

    TRACE << "Opened with " << toc.size() << " tracks\n";

    LocalAudioCD *cd = new LocalAudioCD();
    cd->m_toc = toc;
    cd->m_cdt = cdt;
    cd->m_total_sectors = total_sectors;
    *pcd = AudioCDPtr(cd);
    return 0;
}
Exemplo n.º 6
0
int analyze_cache(cdrom_drive *d, FILE *progress, FILE *log, int speed){

  /* Some assumptions about timing: 

     We can't perform cache determination timing based on looking at
     average transfer times; on slow setups, the speed of a drive
     reading sectors via PIO will not be reliably distinguishable from
     the same drive returning data from the cache via pio.  We need
     something even more noticable and reliable: the seek time. It is
     unlikely we'd ever see a seek latency of under ~10ms given the
     synchronization requirements of a CD and the maximum possible
     rotational velocity. A cache hit would always be faster, even
     with PIO.

     Further complicating things, we have to watch the data collection
     carefully as we're not always going to be on an unloaded system,
     and we even have to guard against other apps accessing the drive
     (something that should never happen on purpose, but could happen
     by accident).  As we know in our testing when seeks should never
     occur, a sudden seek-sized latency popping up in the middle of a
     collection is an indication that collection is possibly invalid.

     A second cause of 'spurious latency' would be media damage; if
     we're consistently hitting latency on the same sector during
     initial collection, may need to move past it. */

  int i,j,ret=0,x;
  int firstsector=-1;
  int lastsector=-1;
  int firsttest=-1;
  int lasttest=-1;
  int offset;
  int warn=0;
  int current=1000;
  int hi=15000;
  int cachesize=0;
  int readahead=0;
  int rollbehind=0;
  int cachegran=0;
  float mspersector=0;
  if(speed<=0)speed=-1;

  reportC("\n=================== Checking drive cache/timing behavior ===================\n");
  d->error_retry=0;

  /* verify the lib and cache analysis match */
  if(strcmp(VERSIONNUM,paranoia_version())){
    reportC("\nWARNING: cdparanoia application (and thus the cache tests) does not match the"
	    "\ninstalled (or in use) libcdda_paranoia.so library.  The final verdict of this"
	    "\ntesting may or may not be accurate for the actual version of the paranoia"
	    "library.  Continuing anyway...\n\n");
  }

  /* find the longest stretch of available audio data */

  for(i=0;i<d->tracks;i++){
    if(cdda_track_audiop(d,i+1)==1){
      if(firsttest == -1)
	firsttest=cdda_track_firstsector(d,i+1);
      lasttest=cdda_track_lastsector(d,i+1);
      if(lasttest-firsttest > lastsector-firstsector){
	firstsector=firsttest;
	lastsector=lasttest;
      }
    }else{
      firsttest=-1;
      lasttest=-1;
    }
  }

  if(firstsector==-1){
    reportC("\n\tNo audio on disc; Cannot determine timing behavior...");
    return -1;
  }

  /* Dump some initial timing data to give a little context for human
     eyes.  Take readings ten minutes apart (45000 sectors) and at end of disk. */
  {
    int best=0;
    int bestcount=0;
    int iterating=0;

    offset = lastsector-firstsector-current-1;

    reportC("\nSeek/read timing:\n");

    while(offset>=firstsector){
      int m = offset/4500;
      int s = (offset-m*4500)/75;
      int f = offset-m*4500-s*75;
      int sofar;

      if(iterating){
	reportC("\n");
      }else{
	printC("\r");
	logC("\n");
      }
      reportC("\t[%02d:%02d.%02d]: ",m,s,f);

      /* initial seek to put at at a small offset past end of upcoming reads */
      if((ret=cdda_read(d,NULL,offset+current+1,1))<0){
	/* media error! grr!  retry elsewhere */
	if(ret==-404)return -1;
	reportC("\n\tWARNING: media error during read; continuing at next offset...");
	offset = (offset-firstsector+44999)/45000*45000+firstsector;
	offset-=45000;
	continue;
      }
  
      sofar=time_drive(d,progress, log, offset, current, 1);
      if(offset==firstsector)mspersector = sofar/(float)current;
      if(sofar==-404)
	return -1;
      else if(sofar<0){
	reportC("\n\tWARNING: media error during read; continuing at next offset...");
	offset = (offset-firstsector+44999)/45000*45000+firstsector;
	offset-=45000;
	continue;
      }else{
	if(!iterating){
	  if(best==0 || sofar*1.01<best){
	    best= sofar;
	    bestcount=0;
	  }else{
	    bestcount+=sofar;
	    if(bestcount>sofar && bestcount>4000)
	      iterating=1;
	  }
	}
      }

      if(iterating){
	offset = (offset-firstsector+44999)/45000*45000+firstsector;
	offset-=45000;
	printC("                 ");
      }else{
	offset--;
	printC(" spinning up...  ");
      }
    }
  }

  reportC("\n\nAnalyzing cache behavior...\n");
  
  /* search on cache size; cache hits are fast, seeks are not, so a
     linear search through cache hits up to a miss are faster than a
     bisection */
  {
    int under=1;
    int onex=0;
    current=0;
    offset = firstsector+10;
    
    while(current <= hi && under){
      int i,j;
      under=0;
      current++;
      
      if(onex){
	if(speed==-1){
	  logC("\tAttempting to reset read speed to full... ");
	}else{
	  logC("\tAttempting to reset read speed to %dx... ",speed);
	}
	if(cdda_speed_set(d,speed)){
	  logC("failed.\n");
	}else{
	  logC("drive said OK\n");
	}
	onex=0;
      }

      printC("\r");
      reportC("\tFast search for approximate cache size... %d sectors            ",current-1);
      logC("\n");
      
      for(i=0;i<25 && !under;i++){
	for(j=0;;j++){
	  int ret1=0,ret2=0;
	  if(i>=15){
	    int sofar=0;
	    
	    if(i==15){
	      printC("\r");
	      reportC("\tSlow verify for approximate cache size... %d sectors",current-1);
	      logC("\n");
	      
	      logC("\tAttempting to reduce read speed to 1x... ");
	      if(cdda_speed_set(d,1)){
		logC("failed.\n");
	      }else{
		logC("drive said OK\n");
	      }
	      onex=1;
	    }
	    printC(".");
	    logC("\t\t>>> ");
	    
	    while(sofar<current){
	      ret1 = cdda_read_timed(d,NULL,offset+sofar,current-sofar,&x);
	      logC("slow_read=%d:%d:%d ",offset+sofar,ret1,x);
	      if(ret1<=0)break;
	      sofar+=ret1;
	    }
	  }else{
	    ret1 = cdda_read_timed(d,NULL,offset+current-1,1,&x);
	    logC("\t\t>>> fast_read=%d:%d:%d ",offset+current-1,ret1,x);

	    /* Some drives are 'easily distracted' when readahead is
	       running ahead of the read cursor, causing accesses of
	       the earliest sectors in the cache to show bursty
	       latency. If there's no seek here after a borderline
	       long read of the earliest sector in the cache, then the
	       cache must not have been dumped yet. */

	    if(ret==1 && i && x<MIN_SEEK_MS){ 
	      under=1;
	      logC("\n");
	      break;
	    }
	  }
	  ret2 = cdda_read_timed(d,NULL,offset,1,&x);
	  logC("seek_read=%d:%d:%d\n",offset,ret2,x);
	  
	  if(ret1<=0 || ret2<=0){
	    offset+=current+100;
	    if(j==10 || offset+current>lastsector){
	      reportC("\n\tToo many read errors while performing drive cache checks;"
		      "\n\t  aborting test.\n\n");
	      return(-1);
	    }
	    reportC("\n\tRead error while performing drive cache checks;"
		    "\n\t  choosing new offset and trying again.\n");
	  }else{
	    if(x==-1){
	      reportC("\n\tTiming error while performing drive cache checks; aborting test.\n");
	      return(-1);
	    }else{
	      if(x<MIN_SEEK_MS){
		under=1;
	      }
	      break;
	    }
	  }
	}
      }
    } 
  }
  cachesize=current-1;

  printC("\r");
  if(cachesize==hi){
    reportC("\tWARNING: Cannot determine drive cache size or behavior!          \n");
    return 1;
  }else if(cachesize){
    reportC("\tApproximate random access cache size: %d sector(s)               \n",cachesize);
  }else{
    reportC("\tDrive does not cache nonlinear access                            \n");
    return 0;
  }
  
  /* does the readahead cache exceed the maximum Paranoia currently expects? */
  {
    cdrom_paranoia *p=paranoia_init(d);
    if(cachesize > paranoia_cachemodel_size(p,-1)){
      reportC("\nWARNING: This drive appears to be caching more sectors of\n"
	      "           readahead than Paranoia can currently handle!\n");
      warn=1;
      
    }
    paranoia_free(p);
  }
  if(speed==-1){
    logC("\tAttempting to reset read speed to full... ");
  }else{
    logC("\tAttempting to reset read speed to %d... ",speed);
  }
  if(cdda_speed_set(d,speed)){
    logC("failed.\n");
  }else{
    logC("drive said OK\n");
  }

  /* This is similar to the Fast search above, but just in case the
     cache is being tracked as multiple areas that are treated
     differently if non-contiguous.... */
  {
    int seekoff = cachesize*3;
    int under=0;
    reportC("\tVerifying that cache is contiguous...");
  
    for(i=0;i<20 && !under;i++){
      printC(".");
      for(j=0;;j++){
	int ret1,ret2;

	if(offset+seekoff>lastsector){
	  reportC("\n\tOut of readable space on CDROM while performing drive checks;"
		  "\n\t  aborting test.\n\n");
	  return(-1);
	}
	

	ret1 = cdda_read_timed(d,NULL,offset+seekoff,1,&x);
	logC("\t\t>>> %d:%d:%d ",offset+seekoff,ret1,x);
	ret2 = cdda_read_timed(d,NULL,offset,1,&x);
	logC("seek_read:%d:%d:%d\n",offset,ret2,x);
	
	if(ret1<=0 || ret2<=0){
	  offset+=cachesize+100;
	  if(j==10){
	    reportC("\n\tToo many read errors while performing drive cache checks;"
		    "\n\t  aborting test.\n\n");
	    return(-1);
	  }
	  reportC("\n\tRead error while performing drive cache checks;"
		  "\n\t  choosing new offset and trying again.\n");
	}else{
	  if(x==-1){
	    reportC("\n\tTiming error while performing drive cache checks; aborting test.\n");
	    return(-1);
	  }else{
	    if(x<MIN_SEEK_MS)under=1;
	    break;
	  }
	}
      }
    }
    printC("\r");
    if(under){
      reportC("\nWARNING: Drive cache does not appear to be contiguous!\n");
      warn=1;
    }else{
      reportC("\tDrive cache tests as contiguous                           \n");
    }
  }

  /* The readahead cache size ascertained above is likely qualified by
     background 'rollahead'; that is, the drive's readahead process is
     often working ahead of our actual linear reads, and if reads stop
     or are interrupted, readahead continues and overflows the cache.
     It is also the case that the cache size we determined above is
     slightly too low because readahead is probably always working
     ahead of reads. 

     Determine the rollahead size a few ways (which may disagree:
     1) Read number of sectors equal to cache size; pause; read backward until seek
     2) Read sectors equal to cache-rollahead; verify reading back to beginning does not seek 
     3) Read sectors equal to cache; pause; read ahead until seek delay
  */

  {
    int lower=0;
    int gran=64;
    int it=3;
    int tests=0;
    int under=1;
    readahead=0;
    
    while(gran>1 || under){
      tests++;
      if(tests>8 && gran<64){
	gran<<=3;
	tests=0;
	it=3;
      }
      if(gran && !under){
	gran>>=3;
	tests=0;
	if(gran==1)it=10;
      }

      under=0;
      readahead=lower+gran;

      printC("\r");
      logC("\n");
      reportC("\tTesting background readahead past read cursor... %d",readahead);
      printC("           \b\b\b\b\b\b\b\b\b\b\b");
      for(i=0;i<it;i++){
	int sofar=0,ret;
	logC("\n\t\t%d >>> ",i);

	while(sofar<cachesize){
	  ret = cdda_read_timed(d,NULL,offset+sofar,cachesize-sofar,&x);
	  if(ret<=0)goto error;
	  logC("%d:%d:%d ",offset+sofar,ret,x);

	  /* some drives can lose sync and perform an internal resync,
	     which can also cause readahead to restart.  If we see
	     seek-like delays during the initial cahe load, retry the
	     preload */

	  sofar+=ret;
	}
	
	printC(".");

	/* what we'd predict is needed to let the readahead process work. */
	{
	  int usec=mspersector*(readahead)*(6+i)*200;
	  int max= 13000*2*readahead; /* corresponds to .5x */
	  if(usec>max)usec=max;
	  logC("sleep=%dus ",usec);
	  usleep(usec);
	}
	
	/* seek to offset+cachesize+readahead */
	ret = cdda_read_timed(d,NULL,offset+cachesize+readahead-1,1,&x);
	if(ret<=0)break;
	logC("seek=%d:%d:%d",offset+cachesize+readahead-1,ret,x);
	if(x<MIN_SEEK_MS){
	  under=1;
	  break;
	}else if(i%3==1){
	  /* retime the drive just to be conservative */
	  mspersector=retime_drive(d, progress, log, offset, readahead, mspersector);
	}
      }
      
      if(under)
	lower=readahead;

    }
    readahead=lower;
  }
/*
 * Class:     org_tritonus_lowlevel_cdda_cdparanoia_Cdparanoia
 * Method:    readTOC
 * Signature: ([I[I[I[I[Z[Z[Z[I)I
 */
JNIEXPORT jint JNICALL
Java_org_tritonus_lowlevel_cdda_cdparanoia_Cdparanoia_readTOC
(JNIEnv* env, jobject obj,
 jintArray anValues,
 jintArray anStartFrame,
 jintArray anLength,
 jintArray anType,
 jbooleanArray abAudio,
 jbooleanArray abCopy,
 jbooleanArray abPre,
 jintArray anChannels)
{
	handle_t*	handle;
	cdrom_drive*	cdrom;
	int		nFirstTrack;
	int		nLastTrack;
	jint*		pnValues;
	jint*		pnStartFrame;
	jint*		pnLength;
	jint*		pnType;
	jboolean*	pbAudio;
	jboolean*	pbCopy;
	jboolean*	pbPre;
	jint*		pnChannels;
	int		nTrack;

	if (debug_flag) { fprintf(debug_file, "Java_org_tritonus_lowlevel_cdda_cdparanoia_Cdparanoia_readTOC(): begin\n"); }
	handle = getHandle(env, obj);
	cdrom = handle->drive;
	checkArrayLength(env, anValues, 2);
	pnValues = (*env)->GetIntArrayElements(env, anValues, NULL);
	if (pnValues == NULL)
	{
		throwRuntimeException(env, "GetIntArrayElements failed");
	}
	// TODO: check if first track is guaranteed to be 1
	pnValues[0] = 1;
	pnValues[1] = cdda_tracks(cdrom);
	nFirstTrack = 1;
	nLastTrack = cdda_tracks(cdrom);
	(*env)->ReleaseIntArrayElements(env, anValues, pnValues, 0);

	checkArrayLength(env, anStartFrame, 100);
	pnStartFrame = (*env)->GetIntArrayElements(env, anStartFrame, NULL);
	if (pnStartFrame == NULL)
	{
		throwRuntimeException(env, "GetIntArrayElements failed");
	}
	checkArrayLength(env, anLength, 100);
	pnLength = (*env)->GetIntArrayElements(env, anLength, NULL);
	if (pnLength == NULL)
	{
		throwRuntimeException(env, "GetIntArrayElements failed");
	}
	checkArrayLength(env, anType, 100);
	pnType = (*env)->GetIntArrayElements(env, anType, NULL);
	if (pnType == NULL)
	{
		throwRuntimeException(env, "GetIntArrayElements failed");
	}
	checkArrayLength(env, abAudio, 100);
	pbAudio = (*env)->GetBooleanArrayElements(env, abAudio, NULL);
	if (pbAudio == NULL)
	{
		throwRuntimeException(env, "GetBooleanArrayElements failed");
	}
	checkArrayLength(env, abCopy, 100);
	pbCopy = (*env)->GetBooleanArrayElements(env, abCopy, NULL);
	if (pbCopy == NULL)
	{
		throwRuntimeException(env, "GetBooleanArrayElements failed");
	}
	checkArrayLength(env, abPre, 100);
	pbPre = (*env)->GetBooleanArrayElements(env, abPre, NULL);
	if (pbPre == NULL)
	{
		throwRuntimeException(env, "GetBooleanArrayElements failed");
	}
	checkArrayLength(env, anChannels, 100);
	pnChannels = (*env)->GetIntArrayElements(env, anChannels, NULL);
	if (pnChannels == NULL)
	{
		throwRuntimeException(env, "GetIntArrayElements failed");
	}
	for (nTrack = nFirstTrack; nTrack <= nLastTrack; nTrack++)
	{
		pnStartFrame[nTrack - nFirstTrack] = cdda_track_firstsector(cdrom, nTrack);
		pnLength[nTrack - nFirstTrack] = cdda_track_lastsector(cdrom, nTrack) - cdda_track_firstsector(cdrom, nTrack) + 1;
		pnType[nTrack - nFirstTrack] = 0;	// TODO: toc_entry.cdte_ctrl & CDROM_DATA_TRACK;
		pbAudio[nTrack - nFirstTrack] = cdda_track_audiop(cdrom, nTrack);
		pbCopy[nTrack - nFirstTrack] = cdda_track_copyp(cdrom, nTrack);
		pbPre[nTrack - nFirstTrack] = cdda_track_preemp(cdrom, nTrack);
		pnChannels[nTrack - nFirstTrack] = cdda_track_channels(cdrom, nTrack);
		if (debug_flag) { fprintf(debug_file, "Java_org_tritonus_lowlevel_cdda_cdparanoia_Cdparanoia_readTOC(): %d: %d %ld %ld\n", nTrack - nFirstTrack, nTrack, (long) pnStartFrame[nTrack - nFirstTrack], (long) pnLength[nTrack - nFirstTrack]); }
	}

	(*env)->ReleaseIntArrayElements(env, anStartFrame, pnStartFrame, 0);
	(*env)->ReleaseIntArrayElements(env, anLength, pnLength, 0);
	(*env)->ReleaseIntArrayElements(env, anType, pnType, 0);
	(*env)->ReleaseBooleanArrayElements(env, abAudio, pbAudio, 0);
	(*env)->ReleaseBooleanArrayElements(env, abCopy, pbCopy, 0);
	(*env)->ReleaseBooleanArrayElements(env, abPre, pbPre, 0);
	(*env)->ReleaseIntArrayElements(env, anChannels, pnChannels, 0);

	if (debug_flag) { fprintf(debug_file, "Java_org_tritonus_lowlevel_cdda_cdparanoia_Cdparanoia_readTOC(): end\n"); }
	return 0;
}