File indexing completed on 2024-04-21 08:37:51

0001 /* AUDEX CDDA EXTRACTOR
0002  * SPDX-FileCopyrightText: Copyright (C) 2007 Marco Nelles
0003  * <https://userbase.kde.org/Audex>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 
0008 #include "cddaextractthread.h"
0009 #include "utils/cddacdio.h"
0010 
0011 #include <QDebug>
0012 #include <cdio/sector.h>
0013 
0014 static CDDAExtractThread *aet = nullptr;
0015 
0016 void paranoia_callback(long sector, paranoia_cb_mode_t status)
0017 {
0018     aet->create_status(sector, status);
0019 }
0020 
0021 CDDAExtractThread::CDDAExtractThread(QObject *parent, CDDACDIO *cdio)
0022     : QThread(parent)
0023 {
0024     p_cdio = cdio;
0025     if (!p_cdio) {
0026         qDebug() << "Paranoia object not found. low mem?";
0027         Q_EMIT error(i18n("Internal device error."), i18n("Check your device and make a bug report."));
0028         return;
0029     }
0030     connect(p_cdio, SIGNAL(error(const QString &, const QString &)), this, SLOT(slot_error(const QString &, const QString &)));
0031 
0032     overall_sectors_read = 0;
0033     paranoia_full_mode = true;
0034     paranoia_retries = 20;
0035     paranoia_never_skip = false;
0036     skip_reading_errors = false;
0037     sample_offset = 0;
0038     track = 1;
0039     b_first_run = true;
0040     b_interrupt = false;
0041     b_error = false;
0042     status_previous_sector = -1;
0043 
0044     silence.fill(0, CD_FRAMESIZE_RAW);
0045 }
0046 
0047 CDDAExtractThread::~CDDAExtractThread()
0048 {
0049 }
0050 
0051 void CDDAExtractThread::start()
0052 {
0053     QThread::start();
0054 }
0055 
0056 void CDDAExtractThread::run()
0057 {
0058     if (!p_cdio)
0059         return;
0060 
0061     if (b_interrupt)
0062         return;
0063 
0064     b_interrupt = false;
0065     b_error = false;
0066 
0067     paranoia_status_count.clear();
0068     paranoia_status_table.clear();
0069 
0070     if (b_first_run) {
0071         p_log.append(i18n("Drive Vendor: %1, Drive Model: %2, Drive Revision: %3", p_cdio->getVendor(), p_cdio->getModel(), p_cdio->getRevision()));
0072         p_log.append(i18n("TOC:"));
0073         p_log.append(p_cdio->prettyTOC());
0074         b_first_run = false;
0075     }
0076 
0077     if (track == 0) {
0078         first_sector = p_cdio->firstSectorOfDisc();
0079         last_sector = p_cdio->lastSectorOfDisc();
0080     } else {
0081         first_sector = p_cdio->firstSectorOfTrack(track) + sector_offset;
0082         last_sector = p_cdio->lastSectorOfTrack(track) + sector_offset;
0083     }
0084 
0085     if (first_sector < 0 || last_sector < 0) {
0086         Q_EMIT info(i18n("Extracting finished."));
0087         return;
0088     }
0089 
0090     qDebug() << "Track:" << track;
0091     qDebug() << "Sample offset:" << sample_offset;
0092     qDebug() << "Sector offset:" << sector_offset;
0093     qDebug() << "Sample offset fraction:" << sample_offset_fraction;
0094     qDebug() << "First sector:" << first_sector;
0095     qDebug() << "Last sector:" << last_sector;
0096 
0097     // track length
0098     sectors_all = last_sector - first_sector;
0099     sectors_all += sector_offset;
0100     sectors_read = 0;
0101 
0102     p_cdio->enableParanoiaFullMode(paranoia_full_mode);
0103     p_cdio->enableParanoiaNeverSkip(paranoia_never_skip);
0104     p_cdio->setParanoiaMaxRetries(paranoia_retries);
0105 
0106     QString paranoiaErrorMsg;
0107 
0108     p_cdio->paranoiaSeek(first_sector, SEEK_SET);
0109     if (p_cdio->paranoiaError(paranoiaErrorMsg)) {
0110         p_log.append(i18n("Error occured while seeking at sector %1: %2", first_sector, paranoiaErrorMsg));
0111         if (track > 0)
0112             Q_EMIT error(i18n("An error occured while ripping track %1. See log.", track));
0113         else
0114             Q_EMIT error(i18n("An error occured while ripping. See log."));
0115         return;
0116     }
0117 
0118     current_sector = first_sector;
0119 
0120     if (sample_offset > 0)
0121         p_log.append(i18n("Correction sample offset: %1", sample_offset));
0122 
0123     QString min = QString("%1").arg((sectors_all / SECTORS_PER_SECOND) / 60, 2, 10, QChar('0'));
0124     QString sec = QString("%1").arg((sectors_all / SECTORS_PER_SECOND) % 60, 2, 10, QChar('0'));
0125 
0126     // fetch subchannel infos
0127 
0128     if (p_cdio->getDriveCapabilities().contains(READ_MCN) || p_cdio->getDriveCapabilities().contains(READ_ISRC)) {
0129         Q_EMIT info(i18n("Fetching extra information from disc..."));
0130         p_cdio->fetchAndCacheSubchannelInfo();
0131         p_log.append(i18n("Fetching subchannel infos from disc"));
0132     }
0133 
0134     if (track > 0) {
0135         Q_EMIT info(i18n("Ripping track %1 (%2:%3)...", track, min, sec));
0136         p_log.append(i18n("Start reading track %1 with %2 sectors", track, sectors_all));
0137     } else {
0138         Q_EMIT info(i18n("Ripping whole disc (%1:%2)...", min, sec));
0139         p_log.append(i18n("Start reading whole disc with %1 sectors", sectors_all));
0140         p_log.append(i18n("Track %1 with start sector %2", p_cdio->firstTrackNum(), p_cdio->firstSectorOfTrack(p_cdio->firstTrackNum()) + sector_offset));
0141     }
0142 
0143     p_log.append(i18n("First sector: %1, Last sector: %2", first_sector, last_sector));
0144 
0145     p_cdio->mediaChanged();
0146 
0147     bool overread = false;
0148     while (current_sector <= last_sector || overread) {
0149         if (b_interrupt) {
0150             qDebug() << "Interrupt ripping";
0151             break;
0152         }
0153 
0154         // let the global paranoia callback have access to this to emit signals
0155         aet = this;
0156 
0157         if (p_cdio->mediaChanged()) {
0158             b_interrupt = true;
0159             continue;
0160         }
0161 
0162         int16_t *buf = p_cdio->paranoiaRead(paranoia_callback);
0163         if (p_cdio->paranoiaError(paranoiaErrorMsg)) {
0164             p_log.append(i18n("Error occured while reading sector %1 (track time pos %2): %3",
0165                               current_sector,
0166                               CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-')),
0167                               paranoiaErrorMsg));
0168             if (!skip_reading_errors) {
0169                 if (track > 0)
0170                     Q_EMIT error(i18n("An error occured while ripping track %1 at position %2. See log.",
0171                                       track,
0172                                       CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0173                 else
0174                     Q_EMIT error(i18n("An error occured while ripping at position %1. See log.", CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0175                 b_error = true;
0176                 break;
0177             } else {
0178                 if (track > 0)
0179                     Q_EMIT warning(i18n("An error occured while ripping track %1 at position %2. See log.",
0180                                         track,
0181                                         CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0182                 else
0183                     Q_EMIT warning(
0184                         i18n("An error occured while ripping at position %1. See log.", CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0185             }
0186         }
0187         if (!buf) {
0188             if (paranoiaErrorMsg.isEmpty()) {
0189                 p_log.append(i18n("Error reading sector %1 (track time pos %2)", current_sector, CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0190                 if (paranoia_never_skip)
0191                     Q_EMIT error(i18n("An error occured while ripping at position %1. See log.", CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0192                 else
0193                     Q_EMIT warning(
0194                         i18n("An error occured while ripping at position %1. See log.", CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0195             }
0196             if (!skip_reading_errors) {
0197                 b_error = true;
0198                 break;
0199             }
0200             p_log.append(i18n("Error reading sector %1 (%2): **Filling whole sector with silence**",
0201                               current_sector,
0202                               CDDACDIO::LSN2MSF(current_sector - first_sector, QChar('-'))));
0203             buf = reinterpret_cast<int16_t *>(silence.data());
0204         }
0205 
0206         if (sample_offset_fraction > 0 && current_sector == first_sector && track > 0) {
0207             Q_EMIT output(QByteArray((const char *)buf + (sample_offset_fraction * 4), CD_FRAMESIZE_RAW - (sample_offset_fraction * 4)));
0208         } else if (sample_offset_fraction < 0 && current_sector == first_sector && track > 0) {
0209             Q_EMIT output(QByteArray((const char *)buf + (CD_FRAMESIZE_RAW - (-sample_offset_fraction * 4)), (-sample_offset_fraction * 4)));
0210         } else if (sample_offset_fraction < 0 && current_sector == last_sector && track > 0) {
0211             Q_EMIT output(QByteArray((const char *)buf, CD_FRAMESIZE_RAW - (-sample_offset_fraction * 4)));
0212         } else if (overread) {
0213             Q_EMIT output(QByteArray((const char *)buf, sample_offset_fraction * 4));
0214             overread = false;
0215         } else {
0216             Q_EMIT output(QByteArray((const char *)buf, CD_FRAMESIZE_RAW));
0217         }
0218 
0219         // if we have a positive sample offset we need to overread at the end
0220         if (sample_offset > 0 && current_sector == last_sector && track > 0) {
0221             if (p_cdio->mediaChanged()) {
0222                 b_interrupt = true;
0223                 break;
0224             }
0225             if (p_cdio->isLastTrack(track)) { // if we read into the leadout at the end of the disc then..
0226                 p_cdio->paranoiaSeek(current_sector, SEEK_SET); // flush the buffer (paranoia internal)
0227                 if (p_cdio->paranoiaError(paranoiaErrorMsg)) {
0228                     p_log.append(i18n("Error occured while seeking at sector %1: %2", current_sector, paranoiaErrorMsg));
0229                     if (track > 0)
0230                         Q_EMIT error(i18n("An error occured while ripping track %1. See log.", track));
0231                     else
0232                         Q_EMIT error(i18n("An error occured while ripping. See log."));
0233                     b_error = true;
0234                     break;
0235                 }
0236             }
0237             overread = true;
0238         }
0239 
0240         ++current_sector;
0241 
0242         ++sectors_read;
0243         ++overall_sectors_read;
0244         float fraction = 0.0f;
0245         if (sectors_all > 0)
0246             fraction = (float)sectors_read / (float)sectors_all;
0247         Q_EMIT progress((int)(100.0f * fraction), current_sector, overall_sectors_read);
0248     }
0249 
0250     if (b_error) {
0251         Q_EMIT error(i18n("Ripping was canceled due to an error."));
0252         p_log.append(i18n("Ripping was canceled due to an error."));
0253     } else if (b_interrupt) {
0254         Q_EMIT error(i18n("User canceled extracting."));
0255         p_log.append(i18n("Extraction interrupted"));
0256     } else {
0257         if (track > 0) {
0258             Q_EMIT info(i18n("Ripping OK (Track %1).", track));
0259         } else {
0260             Q_EMIT info(i18n("Ripping OK."));
0261         }
0262         p_log.append(i18n("Ripping finished"));
0263     }
0264 }
0265 
0266 void CDDAExtractThread::cancel()
0267 {
0268     b_interrupt = true;
0269 }
0270 
0271 bool CDDAExtractThread::isProcessing()
0272 {
0273     return !(b_interrupt || !isRunning());
0274 }
0275 
0276 const QStringList &CDDAExtractThread::log()
0277 {
0278     return p_log;
0279 }
0280 
0281 void CDDAExtractThread::reset()
0282 {
0283     overall_sectors_read = 0;
0284     paranoia_full_mode = true;
0285     paranoia_retries = 20;
0286     paranoia_never_skip = true;
0287     sample_offset = 0;
0288     track = 1;
0289     b_first_run = true;
0290     b_interrupt = false;
0291     b_error = false;
0292     status_previous_sector = -1;
0293 }
0294 
0295 void CDDAExtractThread::slot_error(const QString &message, const QString &details)
0296 {
0297     Q_EMIT error(message, details);
0298 }
0299 
0300 void CDDAExtractThread::create_status(long sector, paranoia_cb_mode_t status)
0301 {
0302     if (status == PARANOIA_CB_READ || status == PARANOIA_CB_FINISHED)
0303         return;
0304 
0305     paranoia_status_count[status]++;
0306     paranoia_status_table.insert(sector, status);
0307 }
0308 
0309 /* Paranoia status explanations:
0310  *
0311  * PARANOIA_CB_READ             : No error.
0312  * PARANOIA_CB_VERIFY           : No error. Verifying jitter
0313  * PARANOIA_CB_FIXUP_EDGE       : Recoverable minor error. Fixed edge jitter.
0314  * PARANOIA_CB_FIXUP_ATOM       : Recoverable minor error. Fixed atom jitter.
0315  * PARANOIA_CB_SCRATCH          : Unsupported with current paranoia implementation. Should not occur.
0316  * PARANOIA_CB_REPAIR           : Unsupported with current paranoia implementation. Should not occur.
0317  * PARANOIA_CB_SKIP             : Error. Skipped sector.
0318  * PARANOIA_CB_DRIFT            : Error. Skipped sector.
0319  * PARANOIA_CB_BACKOFF          : Unsupported with current paranoia implementation. Should not occur.
0320  * PARANOIA_CB_OVERLAP          : No error. Dynamic overlap adjust. Sector does not seem to contain the current sector but the amount of overlapped data.
0321  * PARANOIA_CB_FIXUP_DROPPED    : Recoverable error. Fixed dropped bytes.
0322  * PARANOIA_CB_FIXUP_DUPED      : Recoverable error. Fixed duplicate bytes.
0323  * PARANOIA_CB_READERR          : Error. Reading error.
0324  * PARANOIA_CB_CACHEERR         : Cache error?
0325  * PARANOIA_CB_WROTE            : No error.
0326  * PARANOIA_CB_FINISHED         : No error. Just finished ripping.
0327  */