/* * * $Id: k3bcdparanoialib.cpp 621693 2007-01-09 14:38:25Z trueg $ * Copyright (C) 2003-2007 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * This program 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. * See the file "COPYING" for the exact licensing terms. */ #include #include "k3bcdparanoialib.h" #include #include #include #include #include #include #include static bool s_haveLibCdio = false; void* K3bCdparanoiaLib::s_libInterface = 0; void* K3bCdparanoiaLib::s_libParanoia = 0; int K3bCdparanoiaLib::s_counter = 0; #define CDDA_IDENTIFY s_haveLibCdio ? "cdio_cddap_identify" : "cdda_identify" #define CDDA_CLOSE s_haveLibCdio ? "cdio_cddap_close" : "cdda_close" #define CDDA_OPEN s_haveLibCdio ? "cdio_cddap_open" : "cdda_open" #define CDDA_TRACK_FIRSTSECTOR s_haveLibCdio ? "cdio_cddap_track_firstsector" : "cdda_track_firstsector" #define CDDA_TRACK_LASTSECTOR s_haveLibCdio ? "cdio_cddap_track_lastsector" : "cdda_track_lastsector" #define CDDA_VERBOSE_SET s_haveLibCdio ? "cdio_cddap_verbose_set" : "cdda_verbose_set" #define CDDA_DISC_FIRSTSECTOR s_haveLibCdio ? "cdio_cddap_disc_firstsector" : "cdda_disc_firstsector" #define PARANOIA_INIT s_haveLibCdio ? "cdio_paranoia_init" : "paranoia_init" #define PARANOIA_FREE s_haveLibCdio ? "cdio_paranoia_free" : "paranoia_free" #define PARANOIA_MODESET s_haveLibCdio ? "cdio_paranoia_modeset" : "paranoia_modeset" #define PARANOIA_SEEK s_haveLibCdio ? "cdio_paranoia_seek" : "paranoia_seek" #define PARANOIA_READ_LIMITED s_haveLibCdio ? "cdio_paranoia_read_limited" : "paranoia_read_limited" // from cdda_paranoia.h #define PARANOIA_CB_READ 0 #define PARANOIA_CB_VERIFY 1 #define PARANOIA_CB_FIXUP_EDGE 2 #define PARANOIA_CB_FIXUP_ATOM 3 #define PARANOIA_CB_SCRATCH 4 #define PARANOIA_CB_REPAIR 5 #define PARANOIA_CB_SKIP 6 #define PARANOIA_CB_DRIFT 7 #define PARANOIA_CB_BACKOFF 8 #define PARANOIA_CB_OVERLAP 9 #define PARANOIA_CB_FIXUP_DROPPED 10 #define PARANOIA_CB_FIXUP_DUPED 11 #define PARANOIA_CB_READERR 12 static void paranoiaCallback( long, int status ) { // do nothing so far.... return; switch( status ) { case -1: break; case -2: break; case PARANOIA_CB_READ: // no problem // does only this mean that the sector has been read? // m_lastReadSector = sector; // this seems to be rather useless // m_readSectors++; break; case PARANOIA_CB_VERIFY: break; case PARANOIA_CB_FIXUP_EDGE: break; case PARANOIA_CB_FIXUP_ATOM: break; case PARANOIA_CB_SCRATCH: // scratch detected break; case PARANOIA_CB_REPAIR: break; case PARANOIA_CB_SKIP: // skipped sector break; case PARANOIA_CB_DRIFT: break; case PARANOIA_CB_BACKOFF: break; case PARANOIA_CB_OVERLAP: // sector does not seem to contain the current // sector but the amount of overlapped data // m_overlap = sector; break; case PARANOIA_CB_FIXUP_DROPPED: break; case PARANOIA_CB_FIXUP_DUPED: break; case PARANOIA_CB_READERR: break; } } extern "C" { struct cdrom_drive; struct cdrom_paranoia; // HINT: these pointers must NOT have the same name like the actual methods! // I added "cdda_" as prefix // Before doing that K3b crashed in cdda_open! // Can anyone please explain that to me? // cdda_interface cdrom_drive* (*cdda_cdda_identify)(const char*, int, char**); int (*cdda_cdda_open)(cdrom_drive *d); int (*cdda_cdda_close)(cdrom_drive *d); long (*cdda_cdda_track_firstsector)( cdrom_drive*, int ); long (*cdda_cdda_track_lastsector)( cdrom_drive*, int ); long (*cdda_cdda_disc_firstsector)(cdrom_drive *d); void (*cdda_cdda_verbose_set)(cdrom_drive *d,int err_action, int mes_action); // cdda_paranoia cdrom_paranoia* (*cdda_paranoia_init)(cdrom_drive*); void (*cdda_paranoia_free)(cdrom_paranoia *p); void (*cdda_paranoia_modeset)(cdrom_paranoia *p, int mode); int16_t* (*cdda_paranoia_read_limited)(cdrom_paranoia *p, void(*callback)(long,int), int); long (*cdda_paranoia_seek)(cdrom_paranoia *p,long seek,int mode); } // from cdda_paranoia.h #define PARANOIA_MODE_FULL 0xff #define PARANOIA_MODE_DISABLE 0 #define PARANOIA_MODE_VERIFY 1 #define PARANOIA_MODE_FRAGMENT 2 #define PARANOIA_MODE_OVERLAP 4 #define PARANOIA_MODE_SCRATCH 8 #define PARANOIA_MODE_REPAIR 16 #define PARANOIA_MODE_NEVERSKIP 32 /** * Internal class used by K3bCdparanoiaLib */ class K3bCdparanoiaLibData { public: K3bCdparanoiaLibData( K3bDevice::Device* dev ) : m_device(dev), m_drive(0), m_paranoia(0), m_currentSector(0) { s_dataMap.insert( dev, this ); } ~K3bCdparanoiaLibData() { paranoiaFree(); s_dataMap.erase( m_device ); } K3bDevice::Device* device() const { return m_device; } void paranoiaModeSet( int ); bool paranoiaInit(); void paranoiaFree(); int16_t* paranoiaRead( void(*callback)(long,int), int maxRetries ); long paranoiaSeek( long, int ); long firstSector( int ); long lastSector( int ); long sector() const { return m_currentSector; } static K3bCdparanoiaLibData* data( K3bDevice::Device* dev ) { TQMap::const_iterator it = s_dataMap.find( dev ); if( it == s_dataMap.constEnd() ) return new K3bCdparanoiaLibData( dev ); else return *it; } static void freeAll() { // clean up all K3bCdparanoiaLibData instances for( TQMap::iterator it = s_dataMap.begin(); it != s_dataMap.end(); ++it ) delete it.data(); } private: // // We have exactly one instance of K3bCdparanoiaLibData per device // static TQMap s_dataMap; K3bDevice::Device* m_device; cdrom_drive* m_drive; cdrom_paranoia* m_paranoia; long m_currentSector; TQMutex mutex; }; TQMap K3bCdparanoiaLibData::s_dataMap; bool K3bCdparanoiaLibData::paranoiaInit() { mutex.lock(); if( m_drive ) paranoiaFree(); // since we use cdparanoia to open the device it is important to close // the device here m_device->close(); m_drive = cdda_cdda_identify( TQFile::encodeName(m_device->blockDeviceName()), 0, 0 ); if( m_drive == 0 ) { mutex.unlock(); return false; } // cdda_cdda_verbose_set( m_drive, 1, 1 ); cdda_cdda_open( m_drive ); m_paranoia = cdda_paranoia_init( m_drive ); if( m_paranoia == 0 ) { mutex.unlock(); paranoiaFree(); return false; } m_currentSector = 0; mutex.unlock(); return true; } void K3bCdparanoiaLibData::paranoiaFree() { mutex.lock(); if( m_paranoia ) { cdda_paranoia_free( m_paranoia ); m_paranoia = 0; } if( m_drive ) { cdda_cdda_close( m_drive ); m_drive = 0; } mutex.unlock(); } void K3bCdparanoiaLibData::paranoiaModeSet( int mode ) { mutex.lock(); cdda_paranoia_modeset( m_paranoia, mode ); mutex.unlock(); } int16_t* K3bCdparanoiaLibData::paranoiaRead( void(*callback)(long,int), int maxRetries ) { if( m_paranoia ) { mutex.lock(); int16_t* data = cdda_paranoia_read_limited( m_paranoia, callback, maxRetries ); if( data ) m_currentSector++; mutex.unlock(); return data; } else return 0; } long K3bCdparanoiaLibData::firstSector( int track ) { if( m_drive ) { mutex.lock(); long sector = cdda_cdda_track_firstsector( m_drive, track ); mutex.unlock(); return sector; } else return -1; } long K3bCdparanoiaLibData::lastSector( int track ) { if( m_drive ) { mutex.lock(); long sector = cdda_cdda_track_lastsector(m_drive, track ); mutex.unlock(); return sector; } else return -1; } long K3bCdparanoiaLibData::paranoiaSeek( long sector, int mode ) { if( m_paranoia ) { mutex.lock(); m_currentSector = cdda_paranoia_seek( m_paranoia, sector, mode ); mutex.unlock(); return m_currentSector; } else return -1; } class K3bCdparanoiaLib::Private { public: Private() : device(0), currentSector(0), startSector(0), lastSector(0), status(S_OK), paranoiaLevel(0), neverSkip(true), maxRetries(5), data(0) { } ~Private() { } void updateParanoiaMode() { // from cdrdao 1.1.7 int paranoiaMode = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP; switch( paranoiaLevel ) { case 0: paranoiaMode = PARANOIA_MODE_DISABLE; break; case 1: paranoiaMode |= PARANOIA_MODE_OVERLAP; paranoiaMode &= ~PARANOIA_MODE_VERIFY; break; case 2: paranoiaMode &= ~(PARANOIA_MODE_SCRATCH|PARANOIA_MODE_REPAIR); break; } if( neverSkip ) paranoiaMode |= PARANOIA_MODE_NEVERSKIP; data->paranoiaModeSet( paranoiaMode ); } // high-level api K3bDevice::Device* device; K3bDevice::Toc toc; long currentSector; long startSector; long lastSector; int status; unsigned int currentTrack; int paranoiaLevel; bool neverSkip; int maxRetries; K3bCdparanoiaLibData* data; }; K3bCdparanoiaLib::K3bCdparanoiaLib() { d = new Private(); s_counter++; } K3bCdparanoiaLib::~K3bCdparanoiaLib() { delete d; s_counter--; if( s_counter == 0 ) { K3bCdparanoiaLibData::freeAll(); // cleanup the dynamically loaded lib dlclose( s_libInterface ); dlclose( s_libParanoia ); s_libInterface = 0; s_libParanoia = 0; } } bool K3bCdparanoiaLib::load() { cdda_cdda_identify = (cdrom_drive* (*) (const char*, int, char**))dlsym( s_libInterface, CDDA_IDENTIFY ); cdda_cdda_open = (int (*) (cdrom_drive*))dlsym( s_libInterface, CDDA_OPEN ); cdda_cdda_close = (int (*) (cdrom_drive*))dlsym( s_libInterface, CDDA_CLOSE ); cdda_cdda_track_firstsector = (long (*)(cdrom_drive*, int))dlsym( s_libInterface, CDDA_TRACK_FIRSTSECTOR ); cdda_cdda_track_lastsector = (long (*)(cdrom_drive*, int))dlsym( s_libInterface, CDDA_TRACK_LASTSECTOR ); cdda_cdda_verbose_set = (void (*)(cdrom_drive *d,int err_action, int mes_action))dlsym( s_libInterface, CDDA_VERBOSE_SET ); cdda_cdda_disc_firstsector = (long (*)(cdrom_drive *d))dlsym( s_libInterface, CDDA_DISC_FIRSTSECTOR ); cdda_paranoia_init = (cdrom_paranoia* (*)(cdrom_drive*))dlsym( s_libParanoia, PARANOIA_INIT ); cdda_paranoia_free = (void (*)(cdrom_paranoia *p))dlsym( s_libParanoia, PARANOIA_FREE ); cdda_paranoia_modeset = (void (*)(cdrom_paranoia *p, int mode))dlsym( s_libParanoia, PARANOIA_MODESET ); cdda_paranoia_read_limited = (int16_t* (*)(cdrom_paranoia *p, void(*callback)(long,int), int))dlsym( s_libParanoia, PARANOIA_READ_LIMITED ); cdda_paranoia_seek = (long (*)(cdrom_paranoia *p,long seek,int mode))dlsym( s_libParanoia, PARANOIA_SEEK ); // check if all symbols could be resoled if( cdda_cdda_identify == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'cdda_identify'" << endl; return false; } if( cdda_cdda_open == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'cdda_open'" << endl; return false; } if( cdda_cdda_close == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'cdda_close'" << endl; return false; } if( cdda_cdda_track_firstsector == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'cdda_track_firstsector'" << endl; return false; } if( cdda_cdda_track_lastsector == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'cdda_track_lastsector'" << endl; return false; } if( cdda_cdda_disc_firstsector == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'cdda_disc_firstsector'" << endl; return false; } if( cdda_cdda_verbose_set == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'cdda_verbose_set'" << endl; return false; } if( cdda_paranoia_init == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'paranoia_init'" << endl; return false; } if( cdda_paranoia_free == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'paranoia_free'" << endl; return false; } if( cdda_paranoia_modeset == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'paranoia_modeset'" << endl; return false; } if( cdda_paranoia_read_limited == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'paranoia_read_limited'" << endl; return false; } if( cdda_paranoia_seek == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve 'paranoia_seek'" << endl; return false; } return true; } K3bCdparanoiaLib* K3bCdparanoiaLib::create() { // check if libcdda_interface is avalilable if( s_libInterface == 0 ) { s_haveLibCdio = false; s_libInterface = dlopen( "libcdda_interface.so.0", RTLD_NOW|RTLD_GLOBAL ); // try the redhat & Co. location if( s_libInterface == 0 ) s_libInterface = dlopen( "cdda/libcdda_interface.so.0", RTLD_NOW|RTLD_GLOBAL ); // try the new cdio lib if( s_libInterface == 0 ) { s_libInterface = dlopen( "libcdio_cdda.so", RTLD_NOW|RTLD_GLOBAL ); s_haveLibCdio = true; } if( s_libInterface == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error while loading libcdda_interface. " << endl; return 0; } s_libParanoia = dlopen( "libcdda_paranoia.so.0", RTLD_NOW ); // try the redhat & Co. location if( s_libParanoia == 0 ) s_libParanoia = dlopen( "cdda/libcdda_paranoia.so.0", RTLD_NOW ); // try the new cdio lib if( s_haveLibCdio && s_libParanoia == 0 ) s_libParanoia = dlopen( "libcdio_paranoia.so.0", RTLD_NOW ); if( s_libParanoia == 0 ) { kdDebug() << "(K3bCdparanoiaLib) Error while loading libcdda_paranoia. " << endl; dlclose( s_libInterface ); s_libInterface = 0; return 0; } } K3bCdparanoiaLib* lib = new K3bCdparanoiaLib(); if( !lib->load() ) { kdDebug() << "(K3bCdparanoiaLib) Error: could not resolve all symbols!" << endl; delete lib; return 0; } return lib; } bool K3bCdparanoiaLib::initParanoia( K3bDevice::Device* dev, const K3bDevice::Toc& toc ) { if( !dev ) { kdError() << "(K3bCdparanoiaLib::initParanoia) dev = 0!" << endl; return false; } close(); d->device = dev; d->toc = toc; if( d->toc.isEmpty() ) { kdDebug() << "(K3bCdparanoiaLib) empty toc." << endl; cleanup(); return false; } if( d->toc.contentType() == K3bDevice::DATA ) { kdDebug() << "(K3bCdparanoiaLib) No audio tracks found." << endl; cleanup(); return false; } // // Get the appropriate data instance for this device // d->data = K3bCdparanoiaLibData::data( dev ); if( d->data->paranoiaInit() ) { d->startSector = d->currentSector = d->lastSector = 0; return true; } else { cleanup(); return false; } } bool K3bCdparanoiaLib::initParanoia( K3bDevice::Device* dev ) { return initParanoia( dev, dev->readToc() ); } void K3bCdparanoiaLib::close() { cleanup(); } void K3bCdparanoiaLib::cleanup() { if( d->data ) d->data->paranoiaFree(); d->device = 0; d->currentSector = 0; } bool K3bCdparanoiaLib::initReading() { if( d->device ) { // find first audio track K3bDevice::Toc::const_iterator trackIt = d->toc.begin(); while( (*trackIt).type() != K3bDevice::Track::AUDIO ) { ++trackIt; } long start = (*trackIt).firstSector().lba(); // find last audio track while( trackIt != d->toc.end() && (*trackIt).type() == K3bDevice::Track::AUDIO ) ++trackIt; --trackIt; long end = (*trackIt).lastSector().lba(); return initReading( start, end ); } else { kdDebug() << "(K3bCdparanoiaLib) initReading without initParanoia." << endl; return false; } } bool K3bCdparanoiaLib::initReading( unsigned int track ) { if( d->device ) { if( track <= d->toc.count() ) { const K3bDevice::Track& k3bTrack = d->toc[track-1]; if( k3bTrack.type() == K3bDevice::Track::AUDIO ) { return initReading( k3bTrack.firstSector().lba(), k3bTrack.lastSector().lba() ); } else { kdDebug() << "(K3bCdparanoiaLib) Track " << track << " no audio track." << endl; return false; } } else { kdDebug() << "(K3bCdparanoiaLib) Track " << track << " too high." << endl; return false; } } else { kdDebug() << "(K3bCdparanoiaLib) initReading without initParanoia." << endl; return false; } } bool K3bCdparanoiaLib::initReading( long start, long end ) { kdDebug() << "(K3bCdparanoiaLib) initReading( " << start << ", " << end << " )" << endl; if( d->device ) { if( d->toc.firstSector().lba() <= start && d->toc.lastSector().lba() >= end ) { d->startSector = d->currentSector = start; d->lastSector = end; // determine track number d->currentTrack = 1; while( d->toc[d->currentTrack-1].lastSector() < start ) d->currentTrack++; // let the paranoia stuff point to the startSector d->data->paranoiaSeek( start, SEEK_SET ); return true; } else { kdDebug() << "(K3bCdparanoiaLib) " << start << " and " << end << " out of range." << endl; return false; } } else { kdDebug() << "(K3bCdparanoiaLib) initReading without initParanoia." << endl; return false; } } char* K3bCdparanoiaLib::read( int* statusCode, unsigned int* track, bool littleEndian ) { if( d->currentSector > d->lastSector ) { kdDebug() << "(K3bCdparanoiaLib) finished ripping. read " << (d->currentSector - d->startSector) << " sectors." << endl << " current sector: " << d->currentSector << endl; d->status = S_OK; if( statusCode ) *statusCode = d->status; return 0; } if( d->currentSector != d->data->sector() ) { kdDebug() << "(K3bCdparanoiaLib) need to seek before read. Looks as if we are reusing the paranoia instance." << endl; if( !d->data->paranoiaSeek( d->currentSector, SEEK_SET ) ) return 0; } // // The paranoia data could have been used by someone else before // and setting the paranoia mode is fast // d->updateParanoiaMode(); TQ_INT16* data = d->data->paranoiaRead( paranoiaCallback, d->maxRetries ); char* charData = reinterpret_cast(data); #ifdef WORDS_BIGENDIAN // __BYTE_ORDER == __BIG_ENDIAN if( littleEndian ) { #else if( !littleEndian ) { #endif for( int i = 0; i < CD_FRAMESIZE_RAW-1; i+=2 ) { char b = charData[i]; charData[i] = charData[i+1]; charData[i+1] = b; } } if( data ) d->status = S_OK; else d->status = S_ERROR; // We may skip this sector if we'd like... if( statusCode ) *statusCode = d->status; if( track ) *track = d->currentTrack; d->currentSector++; if( d->toc[d->currentTrack-1].lastSector() < d->currentSector ) d->currentTrack++; return charData; } int K3bCdparanoiaLib::status() const { return d->status; } const K3bDevice::Toc& K3bCdparanoiaLib::toc() const { return d->toc; } long K3bCdparanoiaLib::rippedDataLength() const { return d->lastSector - d->startSector + 1; } void K3bCdparanoiaLib::setParanoiaMode( int m ) { d->paranoiaLevel = m; } void K3bCdparanoiaLib::setNeverSkip( bool b ) { d->neverSkip = b; } void K3bCdparanoiaLib::setMaxRetries( int r ) { d->maxRetries = r; }