Blame lib/cdda_interface/common_interface.c

Packit cb6d3d
/*
Packit cb6d3d
  Copyright (C) 2004, 2005, 2007, 2008, 2010, 2011
Packit cb6d3d
  Rocky Bernstein <rocky@gnu.org>
Packit cb6d3d
  Copyright (C) 1998, 2002 Monty monty@xiph.org
Packit cb6d3d
  
Packit cb6d3d
  This program is free software: you can redistribute it and/or modify
Packit cb6d3d
  it under the terms of the GNU General Public License as published by
Packit cb6d3d
  the Free Software Foundation, either version 3 of the License, or
Packit cb6d3d
  (at your option) any later version.
Packit cb6d3d
Packit cb6d3d
  This program is distributed in the hope that it will be useful,
Packit cb6d3d
  but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit cb6d3d
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit cb6d3d
  GNU General Public License for more details.
Packit cb6d3d
Packit cb6d3d
  You should have received a copy of the GNU General Public License
Packit cb6d3d
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit cb6d3d
*/
Packit cb6d3d
/******************************************************************
Packit cb6d3d
 *
Packit cb6d3d
 * CDROM communication common to all interface methods is done here 
Packit cb6d3d
 * (mostly ioctl stuff, but not ioctls specific to the 'cooked'
Packit cb6d3d
 * interface) 
Packit cb6d3d
 *
Packit cb6d3d
 ******************************************************************/
Packit cb6d3d
Packit cb6d3d
/* common_interface.h has to come first else _FILE_OFFSET_BITS are
Packit cb6d3d
   redefined in say opensolaris. */
Packit cb6d3d
#include "common_interface.h"
Packit cb6d3d
#include <math.h>
Packit cb6d3d
#include "utils.h"
Packit cb6d3d
#include "smallft.h"
Packit cb6d3d
Packit cb6d3d
/* Variables to hold debugger-helping enumerations */
Packit cb6d3d
enum paranoia_cdda_enums;
Packit cb6d3d
enum paranoia_jitter_enums;
Packit cb6d3d
Packit cb6d3d
/*! Determine Endian-ness of the CD-drive based on reading data from
Packit cb6d3d
  it. Some drives return audio data Big Endian while some (most)
Packit cb6d3d
  return data Little Endian. Drives known to return data bigendian are
Packit cb6d3d
  SCSI drives from Kodak, Ricoh, HP, Philips, Plasmon, Grundig
Packit cb6d3d
  CDR100IPW, and Mitsumi CD-R. ATAPI and MMC drives are little endian.
Packit cb6d3d
Packit cb6d3d
  rocky: As someone who didn't write the code, I have to say this is
Packit cb6d3d
  nothing less than brilliant. An FFT is done both ways and the the
Packit cb6d3d
  transform is looked at to see which has data in the FFT (or audible)
Packit cb6d3d
  portion. (Or so that's how I understand it.)
Packit cb6d3d
Packit cb6d3d
  @return 1 if big-endian, 0 if little-endian, -1 if we couldn't
Packit cb6d3d
  figure things out or some error.
Packit cb6d3d
 */
Packit cb6d3d
int 
Packit cb6d3d
data_bigendianp(cdrom_drive_t *d)
Packit cb6d3d
{
Packit cb6d3d
  float lsb_votes=0;
Packit cb6d3d
  float msb_votes=0;
Packit cb6d3d
  int i,checked;
Packit cb6d3d
  int endiancache=d->bigendianp;
Packit cb6d3d
  float *a=calloc(1024,sizeof(float));
Packit cb6d3d
  float *b=calloc(1024,sizeof(float));
Packit cb6d3d
  long readsectors=5;
Packit cb6d3d
  int16_t *buff=malloc(readsectors*CDIO_CD_FRAMESIZE_RAW*sizeof(int16_t));
Packit cb6d3d
  memset(buff, 0, readsectors*CDIO_CD_FRAMESIZE_RAW*sizeof(int16_t));
Packit cb6d3d
Packit cb6d3d
  /* look at the starts of the audio tracks */
Packit cb6d3d
  /* if real silence, tool in until some static is found */
Packit cb6d3d
Packit cb6d3d
  /* Force no swap for now */
Packit cb6d3d
  d->bigendianp=-1;
Packit cb6d3d
  
Packit cb6d3d
  cdmessage(d,"\nAttempting to determine drive endianness from data...");
Packit cb6d3d
  d->enable_cdda(d,1);
Packit cb6d3d
  for(i=0,checked=0;i<d->tracks;i++){
Packit cb6d3d
    float lsb_energy=0;
Packit cb6d3d
    float msb_energy=0;
Packit cb6d3d
    if(cdda_track_audiop(d,i+1)==1){
Packit cb6d3d
      long firstsector=cdda_track_firstsector(d,i+1);
Packit cb6d3d
      long lastsector=cdda_track_lastsector(d,i+1);
Packit cb6d3d
      int zeroflag=-1;
Packit cb6d3d
      long beginsec=0;
Packit cb6d3d
      
Packit cb6d3d
      /* find a block with nonzero data */
Packit cb6d3d
      
Packit cb6d3d
      while(firstsector+readsectors<=lastsector){
Packit cb6d3d
	int j;
Packit cb6d3d
	
Packit cb6d3d
	if(d->read_audio(d,buff,firstsector,readsectors)>0){
Packit cb6d3d
	  
Packit cb6d3d
	  /* Avoid scanning through jitter at the edges */
Packit cb6d3d
	  for(beginsec=0;beginsec
Packit cb6d3d
	    int offset=beginsec*CDIO_CD_FRAMESIZE_RAW/2;
Packit cb6d3d
	    /* Search *half* */
Packit cb6d3d
	    for(j=460;j<128+460;j++)
Packit cb6d3d
	      if(buff[offset+j]!=0){
Packit cb6d3d
		zeroflag=0;
Packit cb6d3d
		break;
Packit cb6d3d
	      }
Packit cb6d3d
	    if(!zeroflag)break;
Packit cb6d3d
	  }
Packit cb6d3d
	  if(!zeroflag)break;
Packit cb6d3d
	  firstsector+=readsectors;
Packit cb6d3d
	}else{
Packit cb6d3d
	  d->enable_cdda(d,0);
Packit cb6d3d
	  free(a);
Packit cb6d3d
	  free(b);
Packit cb6d3d
	  free(buff);
Packit cb6d3d
	  return(-1);
Packit cb6d3d
	}
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      beginsec*=CDIO_CD_FRAMESIZE_RAW/2;
Packit cb6d3d
      
Packit cb6d3d
      /* un-interleave for an FFT */
Packit cb6d3d
      if(!zeroflag){
Packit cb6d3d
	int j;
Packit cb6d3d
	
Packit cb6d3d
	for(j=0;j<128;j++)
Packit cb6d3d
	  a[j] = le16_to_cpu(buff[j*2+beginsec+460]);
Packit cb6d3d
	for(j=0;j<128;j++)
Packit cb6d3d
	  b[j] = le16_to_cpu(buff[j*2+beginsec+461]);
Packit cb6d3d
Packit cb6d3d
	fft_forward(128,a,NULL,NULL);
Packit cb6d3d
	fft_forward(128,b,NULL,NULL);
Packit cb6d3d
Packit cb6d3d
	for(j=0;j<128;j++)
Packit cb6d3d
	  lsb_energy+=fabs(a[j])+fabs(b[j]);
Packit cb6d3d
	
Packit cb6d3d
	for(j=0;j<128;j++)
Packit cb6d3d
	  a[j] = be16_to_cpu(buff[j*2+beginsec+460]);
Packit cb6d3d
Packit cb6d3d
	for(j=0;j<128;j++)
Packit cb6d3d
	  b[j] = be16_to_cpu(buff[j*2+beginsec+461]);
Packit cb6d3d
Packit cb6d3d
	fft_forward(128,a,NULL,NULL);
Packit cb6d3d
	fft_forward(128,b,NULL,NULL);
Packit cb6d3d
Packit cb6d3d
	for(j=0;j<128;j++)
Packit cb6d3d
	  msb_energy+=fabs(a[j])+fabs(b[j]);
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
    if(lsb_energy
Packit cb6d3d
      lsb_votes+=msb_energy/lsb_energy;
Packit cb6d3d
      checked++;
Packit cb6d3d
    }else
Packit cb6d3d
      if(lsb_energy>msb_energy){
Packit cb6d3d
	msb_votes+=lsb_energy/msb_energy;
Packit cb6d3d
	checked++;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
    if(checked==5 && (lsb_votes==0 || msb_votes==0))break;
Packit cb6d3d
    cdmessage(d,".");
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  free(buff);
Packit cb6d3d
  free(a);
Packit cb6d3d
  free(b);
Packit cb6d3d
  d->bigendianp=endiancache;
Packit cb6d3d
  d->enable_cdda(d,0);
Packit cb6d3d
Packit cb6d3d
  /* How did we vote?  Be potentially noisy */
Packit cb6d3d
  if (lsb_votes>msb_votes) {
Packit cb6d3d
    char buffer[256];
Packit cb6d3d
    cdmessage(d,"\n\tData appears to be coming back Little Endian.\n");
Packit cb6d3d
    sprintf(buffer,"\tcertainty: %d%%\n",(int)
Packit cb6d3d
	    (100.*lsb_votes/(lsb_votes+msb_votes)+.5));
Packit cb6d3d
    cdmessage(d,buffer);
Packit cb6d3d
    return(0);
Packit cb6d3d
  } else {
Packit cb6d3d
    if(msb_votes>lsb_votes){
Packit cb6d3d
      char buffer[256];
Packit cb6d3d
      cdmessage(d,"\n\tData appears to be coming back Big Endian.\n");
Packit cb6d3d
      sprintf(buffer,"\tcertainty: %d%%\n",(int)
Packit cb6d3d
	      (100.*msb_votes/(lsb_votes+msb_votes)+.5));
Packit cb6d3d
      cdmessage(d,buffer);
Packit cb6d3d
      return(1);
Packit cb6d3d
    }
Packit cb6d3d
Packit cb6d3d
    cdmessage(d,"\n\tCannot determine CDROM drive endianness.\n");
Packit cb6d3d
    return(bigendianp());
Packit cb6d3d
  }
Packit cb6d3d
}
Packit cb6d3d
Packit cb6d3d
/************************************************************************/
Packit cb6d3d
Packit cb6d3d
/*! Here we fix up a couple of things that will never happen.  yeah,
Packit cb6d3d
   right.  
Packit cb6d3d
Packit cb6d3d
   The multisession stuff is from Hannu code; it assumes it knows the
Packit cb6d3d
   leadout/leadin size.  [I think Hannu refers to Hannu Savolainen
Packit cb6d3d
   from GNU/Linux Kernel code.]
Packit cb6d3d
Packit cb6d3d
   @return -1 if we can't get multisession info, 0 if there is one
Packit cb6d3d
   session only or the last session LBA is the same as the first audio
Packit cb6d3d
   track and 1 if the multi-session lba is higher than first audio track
Packit cb6d3d
*/
Packit cb6d3d
int 
Packit cb6d3d
FixupTOC(cdrom_drive_t *d, track_t i_tracks)
Packit cb6d3d
{
Packit cb6d3d
  int j;
Packit cb6d3d
  
Packit cb6d3d
  /* First off, make sure the 'starting sector' is >=0 */
Packit cb6d3d
  
Packit cb6d3d
  for( j=0; j
Packit cb6d3d
    if (d->disc_toc[j].dwStartSector<0 ) {
Packit cb6d3d
      cdmessage(d,"\n\tTOC entry claims a negative start offset: massaging"
Packit cb6d3d
		".\n");
Packit cb6d3d
      d->disc_toc[j].dwStartSector=0;
Packit cb6d3d
    }
Packit cb6d3d
    if( j<i_tracks-1 && d->disc_toc[j].dwStartSector>
Packit cb6d3d
       d->disc_toc[j+1].dwStartSector ) {
Packit cb6d3d
      cdmessage(d,"\n\tTOC entry claims an overly large start offset: massaging"
Packit cb6d3d
		".\n");
Packit cb6d3d
      d->disc_toc[j].dwStartSector=0;
Packit cb6d3d
    }
Packit cb6d3d
Packit cb6d3d
  }
Packit cb6d3d
  /* Make sure the listed 'starting sectors' are actually increasing.
Packit cb6d3d
     Flag things that are blatant/stupid/wrong */
Packit cb6d3d
  {
Packit cb6d3d
    lsn_t last=d->disc_toc[0].dwStartSector;
Packit cb6d3d
    for ( j=1; j
Packit cb6d3d
      if ( d->disc_toc[j].dwStartSector
Packit cb6d3d
	cdmessage(d,"\n\tTOC entries claim non-increasing offsets: massaging"
Packit cb6d3d
		  ".\n");
Packit cb6d3d
	 d->disc_toc[j].dwStartSector=last;
Packit cb6d3d
	
Packit cb6d3d
      }
Packit cb6d3d
      last=d->disc_toc[j].dwStartSector;
Packit cb6d3d
    }
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  d->audio_last_sector = CDIO_INVALID_LSN;
Packit cb6d3d
  
Packit cb6d3d
  {
Packit cb6d3d
    lsn_t last_ses_lsn;
Packit cb6d3d
    if (cdio_get_last_session (d->p_cdio, &last_ses_lsn) < 0)
Packit cb6d3d
      return -1;
Packit cb6d3d
    
Packit cb6d3d
    /* A Red Book Disc must have only one session, otherwise this is a 
Packit cb6d3d
     * CD Extra */
Packit cb6d3d
    if (last_ses_lsn > d->disc_toc[0].dwStartSector) {
Packit cb6d3d
      /* CD Extra discs have two session, the first one ending after 
Packit cb6d3d
       * the last audio track 
Packit cb6d3d
       * Thus the need to fix the length of the the audio data portion to 
Packit cb6d3d
       * not cross the lead-out of this session */
Packit cb6d3d
      for (j = i_tracks-1; j > 1; j--) {
Packit cb6d3d
	if (cdio_get_track_format(d->p_cdio, j+1) != TRACK_FORMAT_AUDIO && 
Packit cb6d3d
	    cdio_get_track_format(d->p_cdio, j) == TRACK_FORMAT_AUDIO) {
Packit cb6d3d
	  /* First session lead-out is 1:30
Packit cb6d3d
	   * Lead-ins are 1:00
Packit cb6d3d
	   * Every session's first track have a 0:02 pregap
Packit cb6d3d
	   *
Packit cb6d3d
	   * That makes a control data section of (90+60+2)*75 sectors in the 
Packit cb6d3d
	   * last audio track */
Packit cb6d3d
	  const int gap = ((90+60+2) * CDIO_CD_FRAMES_PER_SEC);
Packit cb6d3d
	  
Packit cb6d3d
	  if ((last_ses_lsn - gap >= d->disc_toc[j-1].dwStartSector) &&
Packit cb6d3d
	      (last_ses_lsn - gap < d->disc_toc[j].dwStartSector)) {
Packit cb6d3d
	    d->audio_last_sector = last_ses_lsn - gap - 1;
Packit cb6d3d
	    break;
Packit cb6d3d
	  }
Packit cb6d3d
	}
Packit cb6d3d
      }
Packit cb6d3d
      return 1;
Packit cb6d3d
    }
Packit cb6d3d
  }
Packit cb6d3d
    
Packit cb6d3d
  return 0;
Packit cb6d3d
}
Packit cb6d3d
Packit cb6d3d