/* cdrdao - write audio CD-Rs in disc-at-once mode * * Copyright (C) 1998-2001 Andreas Mueller * * 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. * * This program 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "CDD2600.h" #include "SubChannel.h" #include "PQSubChannel16.h" #include "Toc.h" #include "util.h" CDD2600::CDD2600(ScsiIf *scsiIf, unsigned long options) : CdrDriver(scsiIf, options|OPT_DRV_NO_PREGAP_READ), CDD2600Base(this) { driverName_ = "CDD2600 - Version 1.1"; leadInLength_ = leadOutLength_ = 0; speed_ = 2; simulate_ = 1; encodingMode_ = 0; // reads big endian samples audioDataByteOrder_ = 1; memset(&diskInfo_, 0, sizeof(DiskInfo)); } CDD2600::~CDD2600() { } // static constructor CdrDriver *CDD2600::instance(ScsiIf *scsiIf, unsigned long options) { return new CDD2600(scsiIf, options); } // sets speed // return: 0: OK // 1: illegal speed int CDD2600::speed(int s) { if (s >= 0 && s <= 2) { speed_ = s; return 0; } else if (s > 2) { speed_ = 2; return 0; } else { return 1; } } // loads ('unload' == 0) or ejects ('unload' == 1) tray // return: 0: OK // 1: scsi command failed int CDD2600::loadUnload(int unload) const { unsigned char cmd[10]; memset(cmd, 0, 10); cmd[0] = 0xe7; // MEDIUM LOAD/UNLOAD if (unload) { cmd[8] |= 0x01; } if (sendCmd(cmd, 10, NULL, 0, NULL, 0) != 0) { message(-2, "Cannot load/unload medium."); return 1; } return 0; } // sets various audio play parameters, output channels are set to stereo mode // and given volume // immediate: 0: wait until audio play command finished // 1: command finishs immediately after playback has started // sotc: 0: play across track boundaries // 1: stop playing at track boundaries int CDD2600::modeSelectPlay(int immediate, int sotc, unsigned char volume) { unsigned char mp[16]; memset(mp, 0, 16); mp[0] = 0x0e; // PLAY page code mp[1] = 14; // parameter length if (immediate != 0) { mp[2] |= 0x04; } if (sotc != 0) { mp[2] |= 0x02; } mp[8] = 1; mp[9] = volume; mp[10] = 2; mp[11] = volume; if (setModePage(mp, NULL, NULL, 1) != 0) { message(-2, "Cannot set play parameters."); return 1; } return 0; } int CDD2600::initDao(const Toc *toc) { long n; blockLength_ = AUDIO_BLOCK_LEN; blocksPerWrite_ = scsiIf_->maxDataLen() / blockLength_; assert(blocksPerWrite_ > 0); toc_ = toc; diskInfo(); if (!diskInfo_.valid.empty || !diskInfo_.valid.append) { message(-2, "Cannot determine status of inserted medium."); return 1; } if (!diskInfo_.append) { message(-2, "Inserted medium is not appendable."); return 1; } if (modeSelectBlockSize(blockLength_, 1) != 0 || modeSelectSpeed(-1, speed_, simulate_, 1) != 0 || modeSelectCatalog(toc_) != 0 || readSessionInfo(&leadInLength_, &leadOutLength_, 1) != 0) { return 1; } // allocate buffer for write zeros n = blocksPerWrite_ * blockLength_; delete[] zeroBuffer_; zeroBuffer_ = new char[n]; memset(zeroBuffer_, 0, n); return 0; } int CDD2600::startDao() { long lba; if (writeSession(toc_, multiSession_, diskInfo_.thisSessionLba) != 0) { return 1; } message(2, "Writing lead-in and gap..."); lba = diskInfo_.thisSessionLba - 150 - leadInLength_; // write lead-in if (writeZeros(toc_->leadInMode(), TrackData::SUBCHAN_NONE, lba, lba + 150, leadInLength_) != 0) { flushCache(); return 1; } message(5, "Lba after lead-in: %ld", lba); // write gap (2 seconds) if (writeZeros(toc_->leadInMode(), TrackData::SUBCHAN_NONE, lba, lba + 150, 150) != 0) { flushCache(); return 1; } message(2, ""); return 0; } int CDD2600::finishDao() { long lba = diskInfo_.thisSessionLba + toc_->length().lba(); message(2, "Writing lead-out..."); // write lead-out if (writeZeros(toc_->leadOutMode(), TrackData::SUBCHAN_NONE, lba, lba + 150, leadOutLength_) != 0) { flushCache(); return 1; } message(2, "\nFlushing cache..."); if (flushCache() != 0) { return 1; } message(2, ""); blockLength_ = MODE1_BLOCK_LEN; modeSelectBlockSize(blockLength_, 1); delete[] zeroBuffer_, zeroBuffer_ = NULL; return 0; } void CDD2600::abortDao() { flushCache(); blockLength_ = MODE1_BLOCK_LEN; modeSelectBlockSize(blockLength_, 1); } // Writes data to target, the block length depends on the actual writing mode // and is stored internally. 'len' is number of blocks to write. // 'lba' specifies the next logical block address for writing and is updated // by this function but not used for writing // return: 0: OK // 1: scsi command failed int CDD2600::writeData(TrackData::Mode mode, TrackData::SubChannelMode sm, long &lba, const char *buf, long len) { assert(blocksPerWrite_ > 0); assert(blockLength_ > 0); assert(mode == TrackData::AUDIO); int nwritten = 0; int writeLen = 0; unsigned char cmd[10]; memset(cmd, 0, 10); cmd[0] = 0x2a; // WRITE1 while (len > 0) { writeLen = (len > blocksPerWrite_ ? blocksPerWrite_ : len); cmd[7] = writeLen >> 8; cmd[8] = writeLen & 0xff; if (sendCmd(cmd, 10, (unsigned char *)(buf + (nwritten * blockLength_)), writeLen * blockLength_, NULL, 0) != 0) { message(-2, "Write data failed."); return 1; } lba += writeLen; len -= writeLen; nwritten += writeLen; } return 0; } Toc *CDD2600::readDiskToc(int session, const char *audioFilename) { blockLength_ = AUDIO_BLOCK_LEN; if (modeSelectBlockSize(blockLength_, 1) != 0) { return NULL; } modeSelectSpeed(2, -1, 1, 0); Toc *toc = CdrDriver::readDiskToc(session, audioFilename); setBlockSize(MODE1_BLOCK_LEN); return toc; } Toc *CDD2600::readDisk(int session, const char *fname) { Toc *toc = CdrDriver::readDisk(session, fname); setBlockSize(MODE1_BLOCK_LEN); return toc; } int CDD2600::readIsrc(int trackNr, char *buf) { unsigned char cmd[10]; unsigned short dataLen = 0x30; unsigned char data[0x30]; int i; memset(cmd, 0, 10); cmd[0] = 0x42; // READ SUB CHANNEL cmd[2] = 0x40; // get sub channel data cmd[3] = 0x03; // get ISRC code cmd[6] = trackNr; cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-1, "Cannot read ISRC code."); return 1; } else { if (data[0x08] & 0x80) { for (i = 0; i < 12; i++) { buf[i] = data[0x09 + i]; } buf[12] = 0; } } return 0; } // tries to read catalog number from disk and adds it to 'toc' // return: 1 if valid catalog number was found, else 0 int CDD2600::readCatalog(Toc *toc, long startLba, long endLba) { unsigned char cmd[10]; unsigned short dataLen = 0x30; unsigned char data[0x30]; char catalog[14]; int i; // read sub channel information memset(cmd, 0, 10); cmd[0] = 0x42; // READ SUB CHANNEL cmd[2] = 0x40; // get sub channel data cmd[3] = 0x02; // get media catalog number cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-2, "Cannot read sub channel data."); return 0; } if (data[0x08] & 0x80) { for (i = 0; i < 13; i++) { catalog[i] = data[0x09 + i]; } catalog[13] = 0; if (toc->catalog(catalog) == 0) { return 1; } } return 0; } int CDD2600::analyzeTrack(TrackData::Mode mode, int trackNr, long startLba, long endLba, Msf *index, int *indexCnt, long *pregap, char *isrcCode, unsigned char *ctl) { blockLength_ = AUDIO_BLOCK_LEN; modeSelectBlockSize(blockLength_, 1); int ret = analyzeTrackSearch(mode, trackNr, startLba, endLba, index, indexCnt, pregap, isrcCode, ctl); *isrcCode = 0; if (mode == TrackData::AUDIO) { // read ISRC code from sub channel readIsrc(trackNr, isrcCode); } return ret; } int CDD2600::getTrackIndex(long lba, int *trackNr, int *indexNr, unsigned char *ctl) { long relPos; readBlock(lba); return readSubChannelData(trackNr, indexNr, &relPos, ctl); } int CDD2600::readSubChannelData(int *trackNr, int *indexNr, long *relPos, unsigned char *ctl) { unsigned char cmd[10]; unsigned short dataLen = 0x30; unsigned char data[0x30]; // read sub channel information memset(cmd, 0, 10); cmd[0] = 0x42; // READ SUB CHANNEL cmd[2] = 0x40; // get sub channel data cmd[3] = 0x00; // get sub Q channel data cmd[6] = 0; cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-2, "Cannot read sub Q channel data."); return 1; } *trackNr = data[6]; *indexNr = data[7]; *relPos = 0; *relPos |= data[0x0c] << 24; *relPos |= data[0x0d] << 16; *relPos |= data[0x0e] << 8; *relPos |= data[0x0f]; if (ctl != NULL) { *ctl = data[5] & 0x0f; } return 0; } // reads a single block of length 'blockLength_' from given sector // return: 0: OK // 1: error occured void CDD2600::readBlock(unsigned long sector) { unsigned char cmd[10]; unsigned long dataLen = 2 * blockLength_; unsigned char *data = new unsigned char[dataLen]; // read sub channel information memset(cmd, 0, 10); cmd[0] = 0x28; // READ10 cmd[2] = sector >> 24; cmd[3] = sector >> 16; cmd[4] = sector >> 8; cmd[5] = sector; cmd[7] = 0; cmd[8] = 2; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-2, "Cannot read block - ignored."); } delete[] data; } int CDD2600::nextWritableAddress(long *lba, int showError) { unsigned char cmd[10]; unsigned char data[6]; memset(data, 0, 6); memset(cmd, 0, 10); cmd[0] = 0xe2; // FIRST WRITABLE ADDRESS cmd[3] = 1 /*<< 2*/; // AUDIO // cmd[7] = 1; // NPA cmd[8] = 6; // allocation length if (sendCmd(cmd, 10, NULL, 0, data, 6, showError) != 0) { if (showError) message(-2, "Cannot retrieve next writable address."); return 1; } *lba = (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]; return 0; } // Retrieve disk information. // return: DiskInfo structure or 'NULL' on error DiskInfo *CDD2600::diskInfo() { unsigned char cmd[10]; unsigned char data[34]; long nwa; int i; memset(&diskInfo_, 0, sizeof(DiskInfo)); if (readCapacity(&(diskInfo_.capacity), 0) == 0) { diskInfo_.valid.capacity = 1; } if (readSessionInfo(&leadInLength_, &leadOutLength_, 0) == 0) { diskInfo_.valid.manufacturerId = 1; // start time of lead-in diskInfo_.manufacturerId = Msf(450150 - leadInLength_ - 150 ); diskInfo_.append = 1; // this is for the CDD2000 which does not support // READ DISK INFORMATION } else { diskInfo_.append = 0; // this is for the CDD2000 which does not support // READ DISK INFORMATION } diskInfo_.valid.empty = 1; diskInfo_.valid.append = 1; memset(cmd, 0, 10); memset(data, 0, 4); cmd[0] = 0x43; // READ TOC cmd[6] = 0; cmd[8] = 4; if (sendCmd(cmd, 10, NULL, 0, data, 4, 0) == 0) { message(5, "First track %u, last track %u", data[2], data[3]); diskInfo_.lastTrackNr = data[3]; } else { message(5, "READ TOC (format 0) failed."); } if (diskInfo_.lastTrackNr > 0) { // the lead-in length does not specify the manufacturer ID anymore diskInfo_.valid.manufacturerId = 0; diskInfo_.empty = 0; // CD-R is not empty diskInfo_.diskTocType = 0xff; // undefined if (diskInfo_.lastTrackNr < 99 && nextWritableAddress(&nwa, 0) == 0) { message(5, "NWA: %ld", nwa); diskInfo_.thisSessionLba = nwa; diskInfo_.append = 1; } else { diskInfo_.append = 0; } memset(cmd, 0, 10); memset(data, 0, 12); cmd[0] = 0x43; // READ TOC cmd[6] = 0; cmd[8] = 12; cmd[9] = 1 << 6; if (sendCmd(cmd, 10, NULL, 0, data, 12) == 0) { diskInfo_.sessionCnt = data[3]; diskInfo_.lastSessionLba = (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11]; message(5, "First session %u, last session %u, last session start %ld", data[2], data[3], diskInfo_.lastSessionLba); } else { message(5, "READ TOC (format 1) failed."); } if (diskInfo_.sessionCnt > 0) { int len; CdRawToc *toc = getRawToc(diskInfo_.sessionCnt, &len); if (toc != NULL) { for (i = 0; i < len; i++) { if (toc[i].sessionNr == diskInfo_.sessionCnt) { #if 0 if (toc[i].point == 0xb0 && toc[i].min != 0xff && toc[i].sec != 0xff && toc[i].frame != 0xff) { int m = toc[i].min; int s = toc[i].sec; int f = toc[i].frame; if (m < 90 && s < 60 && f < 75) { diskInfo_.thisSessionLba = Msf(m, s, f).lba(); // + 150 - 150 diskInfo_.thisSessionLba += leadInLength_; } } #endif if (toc[i].point == 0xa0) { diskInfo_.diskTocType = toc[i].psec; } } // The point C0 entry may be only stored in the first session's // lead-in if (toc[i].point == 0xc0 && toc[i].pmin <= 99 && toc[i].psec < 60 && toc[i].pframe < 75) { diskInfo_.manufacturerId = Msf(toc[i].pmin, toc[i].psec, toc[i].pframe); diskInfo_.valid.manufacturerId = 1; } } #if 0 if (diskInfo_.thisSessionLba > 0) { if (diskInfo_.lastTrackNr < 99) diskInfo_.append = 1; } else { message(4, "Did not find BO pointer in session %d.", diskInfo_.sessionCnt); } #endif delete[] toc; } else { message(5, "getRawToc failed."); } } } else { // disk is empty and appendable diskInfo_.empty = 1; } if (diskInfo_.append == 0) diskInfo_.empty = 0; return &diskInfo_; } CdRawToc *CDD2600::getRawToc(int sessionNr, int *len) { unsigned char cmd[10]; unsigned short dataLen; unsigned char *data = NULL;; unsigned char reqData[4]; // buffer for requestion the actual length unsigned char *p = NULL; int i; CdRawToc *rawToc; int entries; assert(sessionNr >= 1); // read disk toc length memset(cmd, 0, 10); cmd[0] = 0x43; // READ TOC cmd[6] = sessionNr; cmd[8] = 4; cmd[9] |= 2 << 6; // get Q subcodes if (sendCmd(cmd, 10, NULL, 0, reqData, 4) != 0) { message(-2, "Cannot read raw disk toc."); return NULL; } dataLen = ((reqData[0] << 8) | reqData[1]) + 2; message(5, "Raw toc data len: %d", dataLen); data = new unsigned char[dataLen]; // read disk toc cmd[7] = dataLen >> 8; cmd[8] = dataLen; if (sendCmd(cmd, 10, NULL, 0, data, dataLen) != 0) { message(-2, "Cannot read raw disk toc."); delete[] data; return NULL; } entries = (((data[0] << 8) | data[1]) - 2) / 11; rawToc = new CdRawToc[entries]; for (i = 0, p = data + 4; i < entries; i++, p += 11 ) { #if 0 message(0, "%d %02x %02d %2x %02d:%02d:%02d %02x %02d:%02d:%02d", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10]); #endif rawToc[i].sessionNr = p[0]; rawToc[i].adrCtl = p[1]; rawToc[i].point = p[3]; rawToc[i].min = p[4]; rawToc[i].sec = p[5]; rawToc[i].frame = p[6]; rawToc[i].pmin = p[8]; rawToc[i].psec = p[9]; rawToc[i].pframe = p[10]; } delete[] data; *len = entries; return rawToc; } long CDD2600::readTrackData(TrackData::Mode mode, TrackData::SubChannelMode, long lba, long len, unsigned char *buf) { unsigned char cmd[10]; long blockLen = 2340; long i; TrackData::Mode actMode; int ok = 0; const unsigned char *sense; int senseLen; int softError; if (setBlockSize(blockLen) != 0) return 0; memset(cmd, 0, 10); cmd[0] = 0x28; // READ10 cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; /* if (mode == TrackData::MODE2_FORM1 || mode == TrackData::MODE2_FORM2) cmd[9] = 1 << 6; // MIX flag */ while (len > 0 && !ok) { cmd[7] = len >> 8; cmd[8] = len; memset(transferBuffer_, 0, len * blockLen); switch (sendCmd(cmd, 10, NULL, 0, transferBuffer_, len * blockLen, 0)) { case 0: ok = 1; break; case 2: softError = 0; sense = scsiIf_->getSense(senseLen); if (senseLen > 0x0c) { if ((sense[2] &0x0f) == 5) { switch (sense[12]) { case 0x64: // Illegal mode for this track softError = 1; break; } } else if ((sense[2] & 0x0f) == 3) { // Medium error switch (sense[12]) { case 0x02: // No seek complete, sector not found case 0x11: // L-EC error return -2; break; } } } if (!softError) { scsiIf_->printError(); return -1; } break; default: message(-2, "Read error at LBA %ld, len %ld", lba, len); return -1; break; } if (!ok) { len--; } } unsigned char *sector = transferBuffer_; for (i = 0; i < len; i++) { actMode = determineSectorMode(sector); if (!(actMode == mode || (mode == TrackData::MODE2_FORM_MIX && (actMode == TrackData::MODE2_FORM1 || actMode == TrackData::MODE2_FORM2)) || (mode == TrackData::MODE1_RAW && actMode == TrackData::MODE1) || (mode == TrackData::MODE2_RAW && (actMode == TrackData::MODE2 || actMode == TrackData::MODE2_FORM1 || actMode == TrackData::MODE2_FORM2)))) { return i; } if (buf != NULL) { switch (mode) { case TrackData::MODE1: memcpy(buf, sector + 4, MODE1_BLOCK_LEN); buf += MODE1_BLOCK_LEN; break; case TrackData::MODE2: case TrackData::MODE2_FORM_MIX: memcpy(buf, sector + 4, MODE2_BLOCK_LEN); buf += MODE2_BLOCK_LEN; break; case TrackData::MODE2_FORM1: memcpy(buf, sector + 12, MODE2_FORM1_DATA_LEN); buf += MODE2_FORM1_DATA_LEN; break; case TrackData::MODE2_FORM2: memcpy(buf, sector + 12, MODE2_FORM2_DATA_LEN); buf += MODE2_FORM2_DATA_LEN; break; case TrackData::MODE1_RAW: case TrackData::MODE2_RAW: memcpy(buf, syncPattern, 12); memcpy(buf + 12, sector, 2340); buf += AUDIO_BLOCK_LEN; break; case TrackData::MODE0: case TrackData::AUDIO: message(-3, "CDD2600::readTrackData: Illegal mode."); return 0; break; } } sector += blockLen; } return len; } int CDD2600::readSubChannels(TrackData::SubChannelMode, long lba, long len, SubChannel ***chans, Sample *audioData) { unsigned char cmd[10]; int tries = 5; int ret; if (setBlockSize(AUDIO_BLOCK_LEN) != 0) return 1; memset(cmd, 0, 10); cmd[0] = 0x28; // READ10 cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba; cmd[7] = len >> 8; cmd[8] = len; do { ret = sendCmd(cmd, 10, NULL, 0, (unsigned char*)audioData, len * AUDIO_BLOCK_LEN, (tries == 1) ? 1 : 0); if (ret != 0 && tries == 1) { message(-2, "Reading of audio data failed at sector %ld.", lba); return 1; } tries--; } while (ret != 0 && tries > 0); *chans = NULL; return 0; } int CDD2600::readAudioRange(ReadDiskInfo *rinfo, int fd, long start, long end, int startTrack, int endTrack, TrackInfo *info) { if (!onTheFly_) { int t; message(1, "Analyzing..."); for (t = startTrack; t <= endTrack; t++) { long totalProgress; totalProgress = t * 1000; totalProgress /= rinfo->tracks; sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 0, totalProgress); message(2, "Track %d...", t + 1); info[t].isrcCode[0] = 0; readIsrc(t + 1, info[t].isrcCode); if (info[t].isrcCode[0] != 0) message(2, "Found ISRC code."); totalProgress = (t + 1) * 1000; totalProgress /= rinfo->tracks; sendReadCdProgressMsg(RCD_ANALYZING, rinfo->tracks, t + 1, 1000, totalProgress); } message(1, "Reading..."); } return CdrDriver::readAudioRangeParanoia(rinfo, fd, start, end, startTrack, endTrack, info); }