File indexing completed on 2024-05-12 04:50:00

0001 /*
0002   Copyright (C) 2000 Rik Hemsley (rikkus) <rik@kde.org>
0003   Copyright (C) 2000, 2001, 2002 Michael Matz <matz@kde.org>
0004   Copyright (C) 2001 Carsten Duvenhorst <duvenhorst@m2.uni-hannover.de>
0005   Copyright (C) 2001 Adrian Schroeter <adrian@suse.de>
0006   Copyright (C) 2003 Richard Lärkäng <richard@goteborg.utfors.se>
0007   Copyright (C) 2003 Scott Wheeler <wheeler@kde.org>
0008   Copyright (C) 2004, 2005 Benjamin Meyer <ben at meyerhome dot net>
0009 
0010   This program is free software; you can redistribute it and/or modify
0011   it under the terms of the GNU General Public License as published by
0012   the Free Software Foundation; either version 2 of the License, or
0013   (at your option) any later version.
0014 
0015   This program is distributed in the hope that it will be useful,
0016   but WITHOUT ANY WARRANTY; without even the implied warranty of
0017   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0018   GNU General Public License for more details.
0019 
0020   You should have received a copy of the GNU General Public License
0021   along with this program; if not, write to the Free Software
0022   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
0023   USA.
0024 */
0025 
0026 #include "encodervorbis.h"
0027 #include "audiocd_vorbis_encoder.h"
0028 
0029 #include <KConfig>
0030 #include <QByteArray>
0031 #include <QDateTime>
0032 #include <QRandomGenerator>
0033 #include <cstdlib>
0034 #include <ctime>
0035 #include <vorbis/vorbisenc.h>
0036 
0037 extern "C" {
0038 AUDIOCDPLUGINS_EXPORT void create_audiocd_encoders(KIO::WorkerBase *worker, QList<AudioCDEncoder *> &encoders)
0039 {
0040     encoders.append(new EncoderVorbis(worker));
0041 }
0042 }
0043 
0044 // these are the approx. bitrates for the current 5 Vorbis modes
0045 static const int vorbis_nominal_bitrates[] = {128, 160, 192, 256, 350};
0046 static const int vorbis_bitrates[] = {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 350};
0047 
0048 class EncoderVorbis::Private {
0049 
0050 public:
0051     ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */
0052     ogg_page og; /* one Ogg bitstream page.  Vorbis packets are inside */
0053     ogg_packet op; /* one raw packet of data for decode */
0054 
0055     vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */
0056     vorbis_comment vc; /* struct that stores all the user comments */
0057 
0058     vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
0059     vorbis_block vb; /* local working space for packet->PCM decode */
0060     bool write_vorbis_comments;
0061     long vorbis_bitrate_lower;
0062     long vorbis_bitrate_upper;
0063     long vorbis_bitrate_nominal;
0064     int vorbis_encode_method;
0065     double vorbis_quality;
0066     int vorbis_bitrate;
0067 };
0068 
0069 EncoderVorbis::EncoderVorbis(KIO::WorkerBase *worker)
0070     : AudioCDEncoder(worker)
0071 {
0072     d = new Private();
0073 }
0074 
0075 EncoderVorbis::~EncoderVorbis()
0076 {
0077     vorbis_info_clear(&d->vi);
0078     vorbis_comment_clear(&d->vc);
0079     delete d;
0080 }
0081 
0082 QWidget *EncoderVorbis::getConfigureWidget(KConfigSkeleton **manager) const
0083 {
0084     (*manager) = Settings::self();
0085     auto config = new EncoderVorbisConfig();
0086     config->kcfg_vorbis_quality->setRange(0.0, 10.0);
0087     config->kcfg_vorbis_quality->setSingleStep(0.1);
0088     config->vorbis_bitrate_settings->hide();
0089     return config;
0090 }
0091 
0092 bool EncoderVorbis::init()
0093 {
0094     vorbis_info_init(&d->vi);
0095     vorbis_comment_init(&d->vc);
0096 
0097     vorbis_comment_add_tag(&d->vc, const_cast<char *>("kde-encoder"), const_cast<char *>("kio_audiocd"));
0098     return true;
0099 }
0100 
0101 void EncoderVorbis::loadSettings()
0102 {
0103     Settings *settings = Settings::self();
0104 
0105     d->vorbis_encode_method = settings->vorbis_enc_method();
0106     d->vorbis_quality = settings->vorbis_quality();
0107 
0108     if (settings->set_vorbis_min_br()) {
0109         d->vorbis_bitrate_lower = vorbis_bitrates[settings->vorbis_min_br()] * 1000;
0110     } else {
0111         d->vorbis_bitrate_lower = -1;
0112     }
0113 
0114     if (settings->set_vorbis_max_br()) {
0115         d->vorbis_bitrate_upper = vorbis_bitrates[settings->vorbis_max_br()] * 1000;
0116     } else {
0117         d->vorbis_bitrate_upper = -1;
0118     }
0119 
0120     // this is such a hack!
0121     if (d->vorbis_bitrate_upper != -1 && d->vorbis_bitrate_lower != -1) {
0122         d->vorbis_bitrate = 104000; // empirically determined ...?!
0123     } else {
0124         d->vorbis_bitrate = 160 * 1000;
0125     }
0126 
0127     if (settings->set_vorbis_nominal_br()) {
0128         d->vorbis_bitrate_nominal = vorbis_nominal_bitrates[settings->vorbis_nominal_br()] * 1000;
0129         d->vorbis_bitrate = d->vorbis_bitrate_nominal;
0130     } else {
0131         d->vorbis_bitrate_nominal = -1;
0132     }
0133 
0134     d->write_vorbis_comments = settings->vorbis_comments();
0135 
0136     // Now that we have read in the settings apply them to the encoder lib
0137     switch (d->vorbis_encode_method) {
0138     case 0:
0139         vorbis_encode_init_vbr(&d->vi, 2, 44100, d->vorbis_quality / 10.0);
0140         break;
0141     case 1:
0142         vorbis_encode_init(&d->vi, 2, 44100, d->vorbis_bitrate_upper, d->vorbis_bitrate_nominal, d->vorbis_bitrate_lower);
0143         break;
0144     }
0145 }
0146 
0147 long EncoderVorbis::flush_vorbis()
0148 {
0149     long processed(0);
0150 
0151     while (vorbis_analysis_blockout(&d->vd, &d->vb) == 1) {
0152         vorbis_analysis(&d->vb, nullptr);
0153         /* Non-ancient case.  */
0154         vorbis_bitrate_addblock(&d->vb);
0155 
0156         while (vorbis_bitrate_flushpacket(&d->vd, &d->op)) {
0157             ogg_stream_packetin(&d->os, &d->op);
0158             while (int result = ogg_stream_pageout(&d->os, &d->og)) {
0159                 if (!result)
0160                     break;
0161 
0162                 char *oggheader = reinterpret_cast<char *>(d->og.header);
0163                 char *oggbody = reinterpret_cast<char *>(d->og.body);
0164 
0165                 if (d->og.header_len) {
0166                     ioWorker->data(QByteArray::fromRawData(oggheader, d->og.header_len));
0167                 }
0168 
0169                 if (d->og.body_len) {
0170                     ioWorker->data(QByteArray::fromRawData(oggbody, d->og.body_len));
0171                 }
0172                 processed += d->og.header_len + d->og.body_len;
0173             }
0174         }
0175     }
0176     return processed;
0177 }
0178 
0179 unsigned long EncoderVorbis::size(long time_secs) const {
0180   long vorbis_size;
0181   switch (d->vorbis_encode_method) {
0182   case 0: // quality based encoding
0183   {
0184     // Estimated numbers based on the Vorbis FAQ:
0185     // https://xiph.org/vorbis/faq/#quality
0186 
0187     static long vorbis_q_bitrate[] = {60, 74, 86, 106, 120, 152, 183, 207, 239, 309, 440};
0188     long quality = static_cast<long>(d->vorbis_quality);
0189     if (quality < 0 || quality > 10)
0190       quality = 3;
0191     vorbis_size = (time_secs * vorbis_q_bitrate[quality] * 1000) / 8;
0192 
0193     break;
0194   }
0195   default: // bitrate based encoding
0196       vorbis_size = (time_secs * d->vorbis_bitrate / 8);
0197       break;
0198   }
0199 
0200   return vorbis_size;
0201 }
0202 
0203 const char *EncoderVorbis::mimeType() const
0204 {
0205     return "audio/x-vorbis+ogg";
0206 }
0207 
0208 long EncoderVorbis::readInit(long /*size*/)
0209 {
0210     ogg_packet header;
0211     ogg_packet header_comm;
0212     ogg_packet header_code;
0213 
0214     vorbis_analysis_init(&d->vd, &d->vi);
0215     vorbis_block_init(&d->vd, &d->vb);
0216 
0217     ogg_stream_init(&d->os, QRandomGenerator::global()->generate());
0218 
0219     vorbis_analysis_headerout(&d->vd, &d->vc, &header, &header_comm, &header_code);
0220 
0221     ogg_stream_packetin(&d->os, &header);
0222     ogg_stream_packetin(&d->os, &header_comm);
0223     ogg_stream_packetin(&d->os, &header_code);
0224 
0225     while (int result = ogg_stream_flush(&d->os, &d->og)) {
0226         if (!result)
0227             break;
0228 
0229         char *oggheader = reinterpret_cast<char *>(d->og.header);
0230         char *oggbody = reinterpret_cast<char *>(d->og.body);
0231 
0232         if (d->og.header_len) {
0233             ioWorker->data(QByteArray::fromRawData(oggheader, d->og.header_len));
0234         }
0235 
0236         if (d->og.body_len) {
0237             ioWorker->data(QByteArray::fromRawData(oggbody, d->og.body_len));
0238         }
0239     }
0240     return 0;
0241 }
0242 
0243 long EncoderVorbis::read(qint16 *buf, int frames)
0244 {
0245     int i;
0246     float **buffer = vorbis_analysis_buffer(&d->vd, frames);
0247 
0248     /* uninterleave samples */
0249     for (i = 0; i < frames; i++) {
0250         buffer[0][i] = buf[2 * i] / 32768.0;
0251         buffer[1][i] = buf[2 * i + 1] / 32768.0;
0252     }
0253 
0254     /* process chunk of data */
0255     vorbis_analysis_wrote(&d->vd, i);
0256     return flush_vorbis();
0257 }
0258 
0259 long EncoderVorbis::readCleanup()
0260 {
0261     // send end-of-stream and flush the encoder
0262     vorbis_analysis_wrote(&d->vd, 0);
0263     long processed = flush_vorbis();
0264     ogg_stream_clear(&d->os);
0265     vorbis_block_clear(&d->vb);
0266     vorbis_dsp_clear(&d->vd);
0267     vorbis_info_clear(&d->vi);
0268     return processed;
0269 }
0270 
0271 void EncoderVorbis::fillSongInfo(KCDDB::CDInfo info, int track, const QString &comment)
0272 {
0273     if (!d->write_vorbis_comments)
0274         return;
0275 
0276     using CommentField = QPair<QByteArray, QVariant>;
0277     QList<CommentField> commentFields;
0278 
0279     commentFields.append(CommentField("TITLE", info.track(track - 1).get(Title)));
0280     commentFields.append(CommentField("ARTIST", info.track(track - 1).get(Artist)));
0281     commentFields.append(CommentField("ALBUM", info.get(Title)));
0282     commentFields.append(CommentField("GENRE", info.get(Genre)));
0283     commentFields.append(CommentField("TRACKNUMBER", QString::number(track)));
0284     commentFields.append(CommentField("COMMENT", comment));
0285 
0286     if (info.get(Year).toInt() > 0) {
0287         const QDateTime dt = QDate(info.get(Year).toInt(), 1, 1).startOfDay();
0288         commentFields.append(CommentField("DATE", QLatin1String(dt.toString(Qt::ISODate).toUtf8().data())));
0289     }
0290 
0291     for (QList<CommentField>::iterator it = commentFields.begin(); it != commentFields.end(); ++it) {
0292         // if the value is not empty
0293         if (!(*it).second.toString().isEmpty()) {
0294             char *key = qstrdup((*it).first.constData());
0295             char *value = qstrdup((*it).second.toString().toUtf8().data());
0296 
0297             vorbis_comment_add_tag(&d->vc, key, value);
0298 
0299             delete[] key;
0300             delete[] value;
0301         }
0302     }
0303 }